feat(crypto-wasm): add deterministic Dilithium3 key derivation and hybrid signatures
This commit enables full wallet recovery from BIP-39 mnemonics by implementing deterministic Dilithium3 key derivation using HKDF-SHA3-256 with domain separation. Changes: - crates/synor-crypto-wasm: Implement deterministic Dilithium keygen - Use HKDF with info="synor:dilithium:v1" for key derivation - Enable pqc_dilithium's crypto_sign_keypair via dilithium_kat cfg flag - Add proper memory zeroization on drop - Add tests for deterministic key generation - apps/web: Update transaction signing for hybrid signatures - Add signTransactionHybrid() for Ed25519 + Dilithium3 signatures - Add createSendTransactionHybrid() for quantum-resistant transactions - Update fee estimation for larger hybrid signature size (~5.5KB/input) - Maintain legacy Ed25519-only functions for backwards compatibility - WASM module: Rebuild with deterministic keygen - Update synor_crypto_bg.wasm with new implementation - Module size reduced to ~470KB (optimized) - Documentation updates: - Update mobile wallet plan: React Native -> Flutter - Add testnet-first approach note - Update explorer frontend progress to 90%
This commit is contained in:
parent
6b5a232a5e
commit
3041c6d654
11 changed files with 766 additions and 310 deletions
13
.cargo/config.toml
Normal file
13
.cargo/config.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Cargo configuration for Synor blockchain
|
||||||
|
#
|
||||||
|
# This file configures the Rust build system with project-specific settings.
|
||||||
|
|
||||||
|
# Enable pqc_dilithium's internal key generation for deterministic Dilithium keys
|
||||||
|
# This allows us to generate Dilithium keypairs from a seed (mnemonic-derived)
|
||||||
|
# which is essential for wallet recovery.
|
||||||
|
[build]
|
||||||
|
rustflags = ["--cfg", "dilithium_kat"]
|
||||||
|
|
||||||
|
# WASM target-specific settings for web wallet
|
||||||
|
[target.wasm32-unknown-unknown]
|
||||||
|
rustflags = ["--cfg", "dilithium_kat"]
|
||||||
|
|
@ -1,16 +1,41 @@
|
||||||
/**
|
/**
|
||||||
* Transaction building utilities.
|
* Transaction building utilities.
|
||||||
|
*
|
||||||
|
* ## Hybrid Signature Support
|
||||||
|
*
|
||||||
|
* Synor transactions require hybrid signatures combining:
|
||||||
|
* - Ed25519 (classical, 64 bytes)
|
||||||
|
* - Dilithium3/ML-DSA-65 (post-quantum, ~3.3KB)
|
||||||
|
*
|
||||||
|
* Both signatures must verify for a transaction to be valid.
|
||||||
|
* This provides defense-in-depth against both classical and quantum attacks.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
|
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
|
||||||
import { hash, sign, type Keypair } from './crypto';
|
import {
|
||||||
|
hash,
|
||||||
|
type Keypair,
|
||||||
|
type HybridSignature,
|
||||||
|
createHybridSignatureLocal,
|
||||||
|
createHybridSignatureSmart,
|
||||||
|
serializeHybridSignature,
|
||||||
|
type SigningConfig,
|
||||||
|
} from './crypto';
|
||||||
import { getClient, type Utxo } from './rpc';
|
import { getClient, type Utxo } from './rpc';
|
||||||
|
|
||||||
export interface TxInput {
|
export interface TxInput {
|
||||||
previousTxId: string;
|
previousTxId: string;
|
||||||
outputIndex: number;
|
outputIndex: number;
|
||||||
|
/** Ed25519 signature (64 bytes hex) - for backwards compatibility */
|
||||||
signature?: string;
|
signature?: string;
|
||||||
|
/** Hybrid signature containing both Ed25519 and Dilithium components */
|
||||||
|
hybridSignature?: {
|
||||||
|
ed25519: string;
|
||||||
|
dilithium: string;
|
||||||
|
};
|
||||||
publicKey?: string;
|
publicKey?: string;
|
||||||
|
/** Dilithium public key (1952 bytes hex) - required for hybrid verification */
|
||||||
|
dilithiumPublicKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TxOutput {
|
export interface TxOutput {
|
||||||
|
|
@ -27,6 +52,8 @@ export interface UnsignedTransaction {
|
||||||
|
|
||||||
export interface SignedTransaction extends UnsignedTransaction {
|
export interface SignedTransaction extends UnsignedTransaction {
|
||||||
id: string;
|
id: string;
|
||||||
|
/** Indicates if this transaction uses hybrid signatures */
|
||||||
|
isHybrid?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synor uses 8 decimal places (like satoshis)
|
// Synor uses 8 decimal places (like satoshis)
|
||||||
|
|
@ -55,11 +82,14 @@ export function fromSomas(somas: bigint): string {
|
||||||
/**
|
/**
|
||||||
* Select UTXOs for a transaction using simple accumulator.
|
* Select UTXOs for a transaction using simple accumulator.
|
||||||
* Returns selected UTXOs and change amount.
|
* Returns selected UTXOs and change amount.
|
||||||
|
*
|
||||||
|
* For hybrid signatures, we account for the larger signature size (~3.4KB per input)
|
||||||
*/
|
*/
|
||||||
export function selectUtxos(
|
export function selectUtxos(
|
||||||
utxos: Utxo[],
|
utxos: Utxo[],
|
||||||
targetAmount: bigint,
|
targetAmount: bigint,
|
||||||
feePerByte: bigint = BigInt(1)
|
feePerByte: bigint = BigInt(1),
|
||||||
|
isHybrid: boolean = true
|
||||||
): { selected: Utxo[]; change: bigint; fee: bigint } | null {
|
): { selected: Utxo[]; change: bigint; fee: bigint } | null {
|
||||||
// Sort by amount descending for efficiency
|
// Sort by amount descending for efficiency
|
||||||
const sorted = [...utxos].sort((a, b) => {
|
const sorted = [...utxos].sort((a, b) => {
|
||||||
|
|
@ -71,13 +101,18 @@ export function selectUtxos(
|
||||||
const selected: Utxo[] = [];
|
const selected: Utxo[] = [];
|
||||||
let accumulated = BigInt(0);
|
let accumulated = BigInt(0);
|
||||||
|
|
||||||
|
// Signature size: Ed25519 = 64 bytes, Dilithium = 3293 bytes
|
||||||
|
// Public key size: Ed25519 = 32 bytes, Dilithium = 1952 bytes
|
||||||
|
const inputSize = isHybrid
|
||||||
|
? 150 + 64 + 3293 + 32 + 1952 // ~5.5KB per hybrid input
|
||||||
|
: 150; // ~150 bytes for Ed25519-only
|
||||||
|
|
||||||
for (const utxo of sorted) {
|
for (const utxo of sorted) {
|
||||||
selected.push(utxo);
|
selected.push(utxo);
|
||||||
accumulated += toSomas(utxo.amount);
|
accumulated += toSomas(utxo.amount);
|
||||||
|
|
||||||
// Estimate fee based on tx size
|
// Estimate fee based on tx size
|
||||||
// ~150 bytes per input, ~34 bytes per output, ~10 bytes overhead
|
const estimatedSize = BigInt(selected.length * inputSize + 2 * 34 + 10);
|
||||||
const estimatedSize = BigInt(selected.length * 150 + 2 * 34 + 10);
|
|
||||||
const fee = estimatedSize * feePerByte;
|
const fee = estimatedSize * feePerByte;
|
||||||
const totalNeeded = targetAmount + fee;
|
const totalNeeded = targetAmount + fee;
|
||||||
|
|
||||||
|
|
@ -182,19 +217,75 @@ export function serializeForSigning(tx: UnsignedTransaction): Uint8Array {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign a transaction.
|
* Sign a transaction with hybrid signatures (Ed25519 + Dilithium3).
|
||||||
|
*
|
||||||
|
* This is the recommended signing method for quantum-resistant transactions.
|
||||||
|
* Both signatures must verify for the transaction to be valid on the network.
|
||||||
|
*
|
||||||
|
* @param tx - The unsigned transaction to sign
|
||||||
|
* @param seed - The 64-byte BIP-39 seed for key derivation
|
||||||
|
* @param ed25519PublicKey - The Ed25519 public key bytes
|
||||||
|
* @param dilithiumPublicKey - The Dilithium public key bytes (1952 bytes)
|
||||||
|
* @param config - Optional signing configuration
|
||||||
|
*/
|
||||||
|
export async function signTransactionHybrid(
|
||||||
|
tx: UnsignedTransaction,
|
||||||
|
seed: Uint8Array,
|
||||||
|
ed25519PublicKey: Uint8Array,
|
||||||
|
dilithiumPublicKey: Uint8Array,
|
||||||
|
config?: SigningConfig
|
||||||
|
): Promise<SignedTransaction> {
|
||||||
|
const serialized = serializeForSigning(tx);
|
||||||
|
const txHash = hash(serialized);
|
||||||
|
|
||||||
|
// Sign each input with hybrid signatures
|
||||||
|
const signedInputs: TxInput[] = [];
|
||||||
|
for (const input of tx.inputs) {
|
||||||
|
// Create hybrid signature (Ed25519 + Dilithium)
|
||||||
|
const hybridSig = config
|
||||||
|
? await createHybridSignatureSmart(txHash, seed, config)
|
||||||
|
: await createHybridSignatureLocal(txHash, seed);
|
||||||
|
|
||||||
|
signedInputs.push({
|
||||||
|
...input,
|
||||||
|
// Include both signature formats for compatibility
|
||||||
|
signature: bytesToHex(hybridSig.ed25519),
|
||||||
|
hybridSignature: {
|
||||||
|
ed25519: bytesToHex(hybridSig.ed25519),
|
||||||
|
dilithium: bytesToHex(hybridSig.dilithium),
|
||||||
|
},
|
||||||
|
publicKey: bytesToHex(ed25519PublicKey),
|
||||||
|
dilithiumPublicKey: bytesToHex(dilithiumPublicKey),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...tx,
|
||||||
|
inputs: signedInputs,
|
||||||
|
id: bytesToHex(txHash),
|
||||||
|
isHybrid: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign a transaction with Ed25519 only (legacy mode).
|
||||||
|
*
|
||||||
|
* @deprecated Use signTransactionHybrid for quantum-resistant transactions.
|
||||||
|
* This function is provided for backwards compatibility only.
|
||||||
*/
|
*/
|
||||||
export async function signTransaction(
|
export async function signTransaction(
|
||||||
tx: UnsignedTransaction,
|
tx: UnsignedTransaction,
|
||||||
keypair: Keypair
|
keypair: Keypair
|
||||||
): Promise<SignedTransaction> {
|
): Promise<SignedTransaction> {
|
||||||
|
// Import the sign function for Ed25519-only signing
|
||||||
|
const { sign } = await import('./crypto');
|
||||||
|
|
||||||
const serialized = serializeForSigning(tx);
|
const serialized = serializeForSigning(tx);
|
||||||
const txHash = hash(serialized);
|
const txHash = hash(serialized);
|
||||||
|
|
||||||
// Sign each input
|
// Sign each input with Ed25519 only
|
||||||
const signedInputs: TxInput[] = [];
|
const signedInputs: TxInput[] = [];
|
||||||
for (const input of tx.inputs) {
|
for (const input of tx.inputs) {
|
||||||
// Create signing message: txHash || inputIndex
|
|
||||||
const signature = await sign(txHash, keypair.privateKey);
|
const signature = await sign(txHash, keypair.privateKey);
|
||||||
|
|
||||||
signedInputs.push({
|
signedInputs.push({
|
||||||
|
|
@ -208,6 +299,7 @@ export async function signTransaction(
|
||||||
...tx,
|
...tx,
|
||||||
inputs: signedInputs,
|
inputs: signedInputs,
|
||||||
id: bytesToHex(txHash),
|
id: bytesToHex(txHash),
|
||||||
|
isHybrid: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,21 +307,87 @@ export async function signTransaction(
|
||||||
* Serialize signed transaction for submission.
|
* Serialize signed transaction for submission.
|
||||||
*/
|
*/
|
||||||
export function serializeTransaction(tx: SignedTransaction): string {
|
export function serializeTransaction(tx: SignedTransaction): string {
|
||||||
// Simplified serialization - in production this would match
|
|
||||||
// the exact binary format expected by the node
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
version: tx.version,
|
version: tx.version,
|
||||||
inputs: tx.inputs,
|
inputs: tx.inputs.map((input) => ({
|
||||||
|
previousTxId: input.previousTxId,
|
||||||
|
outputIndex: input.outputIndex,
|
||||||
|
signature: input.signature,
|
||||||
|
hybridSignature: input.hybridSignature,
|
||||||
|
publicKey: input.publicKey,
|
||||||
|
dilithiumPublicKey: input.dilithiumPublicKey,
|
||||||
|
})),
|
||||||
outputs: tx.outputs.map((o) => ({
|
outputs: tx.outputs.map((o) => ({
|
||||||
address: o.address,
|
address: o.address,
|
||||||
amount: o.amount.toString(),
|
amount: o.amount.toString(),
|
||||||
})),
|
})),
|
||||||
lockTime: tx.lockTime,
|
lockTime: tx.lockTime,
|
||||||
|
isHybrid: tx.isHybrid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* High-level send function.
|
* Wallet data with both Ed25519 and Dilithium keys.
|
||||||
|
*/
|
||||||
|
export interface WalletWithDilithium {
|
||||||
|
seed: Uint8Array;
|
||||||
|
keypair: Keypair;
|
||||||
|
address: string;
|
||||||
|
dilithiumPublicKey: Uint8Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* High-level send function with hybrid signatures.
|
||||||
|
*
|
||||||
|
* This is the recommended way to send transactions for quantum resistance.
|
||||||
|
*
|
||||||
|
* @param fromAddress - The sender's address
|
||||||
|
* @param toAddress - The recipient's address
|
||||||
|
* @param amount - Amount to send in human-readable format
|
||||||
|
* @param wallet - Wallet containing seed, keypair, and Dilithium public key
|
||||||
|
* @param config - Optional signing configuration
|
||||||
|
*/
|
||||||
|
export async function createSendTransactionHybrid(
|
||||||
|
fromAddress: string,
|
||||||
|
toAddress: string,
|
||||||
|
amount: string,
|
||||||
|
wallet: WalletWithDilithium,
|
||||||
|
config?: SigningConfig
|
||||||
|
): Promise<SignedTransaction> {
|
||||||
|
const client = getClient();
|
||||||
|
|
||||||
|
// Get UTXOs
|
||||||
|
const utxos = await client.getUtxos(fromAddress);
|
||||||
|
const targetAmount = toSomas(amount);
|
||||||
|
|
||||||
|
// Select UTXOs (account for larger hybrid signature size)
|
||||||
|
const selection = selectUtxos(utxos, targetAmount, BigInt(1), true);
|
||||||
|
if (!selection) {
|
||||||
|
throw new Error('Insufficient funds');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build transaction
|
||||||
|
const tx = buildTransaction(
|
||||||
|
selection.selected.map((utxo) => ({ utxo })),
|
||||||
|
[{ address: toAddress, amount: targetAmount }],
|
||||||
|
fromAddress,
|
||||||
|
selection.change
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sign with hybrid signatures
|
||||||
|
return signTransactionHybrid(
|
||||||
|
tx,
|
||||||
|
wallet.seed,
|
||||||
|
wallet.keypair.publicKey,
|
||||||
|
wallet.dilithiumPublicKey,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* High-level send function (legacy Ed25519 only).
|
||||||
|
*
|
||||||
|
* @deprecated Use createSendTransactionHybrid for quantum-resistant transactions.
|
||||||
*/
|
*/
|
||||||
export async function createSendTransaction(
|
export async function createSendTransaction(
|
||||||
fromAddress: string,
|
fromAddress: string,
|
||||||
|
|
@ -243,8 +401,8 @@ export async function createSendTransaction(
|
||||||
const utxos = await client.getUtxos(fromAddress);
|
const utxos = await client.getUtxos(fromAddress);
|
||||||
const targetAmount = toSomas(amount);
|
const targetAmount = toSomas(amount);
|
||||||
|
|
||||||
// Select UTXOs
|
// Select UTXOs (Ed25519-only mode)
|
||||||
const selection = selectUtxos(utxos, targetAmount);
|
const selection = selectUtxos(utxos, targetAmount, BigInt(1), false);
|
||||||
if (!selection) {
|
if (!selection) {
|
||||||
throw new Error('Insufficient funds');
|
throw new Error('Insufficient funds');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
104
apps/web/src/wasm/synor_crypto.d.ts
vendored
104
apps/web/src/wasm/synor_crypto.d.ts
vendored
|
|
@ -5,27 +5,35 @@ export class DilithiumSigningKey {
|
||||||
free(): void;
|
free(): void;
|
||||||
[Symbol.dispose](): void;
|
[Symbol.dispose](): void;
|
||||||
/**
|
/**
|
||||||
* Generate a new random Dilithium3 keypair.
|
* Get the public key bytes (1952 bytes for Dilithium3).
|
||||||
*/
|
|
||||||
constructor();
|
|
||||||
/**
|
|
||||||
* Create a keypair from a 32-byte seed.
|
|
||||||
*
|
|
||||||
* The seed is expanded to generate the full keypair deterministically.
|
|
||||||
* This allows recovery of keys from a mnemonic-derived seed.
|
|
||||||
*/
|
|
||||||
static fromSeed(seed: Uint8Array): DilithiumSigningKey;
|
|
||||||
/**
|
|
||||||
* Get the public key bytes.
|
|
||||||
*/
|
*/
|
||||||
publicKey(): Uint8Array;
|
publicKey(): Uint8Array;
|
||||||
/**
|
/**
|
||||||
* Get the secret key bytes.
|
* Get the secret key bytes (4000 bytes for Dilithium3).
|
||||||
*
|
*
|
||||||
* WARNING: Handle with care! The secret key should never be exposed
|
* WARNING: Handle with care! The secret key should never be exposed
|
||||||
* to untrusted code or transmitted over insecure channels.
|
* to untrusted code or transmitted over insecure channels.
|
||||||
*/
|
*/
|
||||||
secretKey(): Uint8Array;
|
secretKey(): Uint8Array;
|
||||||
|
/**
|
||||||
|
* Get the signature size in bytes.
|
||||||
|
*/
|
||||||
|
static signatureSize(): number;
|
||||||
|
/**
|
||||||
|
* Get the public key size in bytes.
|
||||||
|
*/
|
||||||
|
static publicKeySize(): number;
|
||||||
|
/**
|
||||||
|
* Get the secret key size in bytes.
|
||||||
|
*/
|
||||||
|
static secretKeySize(): number;
|
||||||
|
/**
|
||||||
|
* Generate a new random Dilithium3 keypair.
|
||||||
|
*
|
||||||
|
* This creates a new keypair using system entropy. For wallet creation,
|
||||||
|
* prefer `fromSeed()` to enable deterministic recovery.
|
||||||
|
*/
|
||||||
|
constructor();
|
||||||
/**
|
/**
|
||||||
* Sign a message with the Dilithium3 secret key.
|
* Sign a message with the Dilithium3 secret key.
|
||||||
*
|
*
|
||||||
|
|
@ -39,30 +47,28 @@ export class DilithiumSigningKey {
|
||||||
*/
|
*/
|
||||||
verify(message: Uint8Array, signature: Uint8Array): boolean;
|
verify(message: Uint8Array, signature: Uint8Array): boolean;
|
||||||
/**
|
/**
|
||||||
* Get the public key size in bytes.
|
* Create a keypair from a seed (32+ bytes).
|
||||||
|
*
|
||||||
|
* The seed is domain-separated using HKDF-SHA3-256 with
|
||||||
|
* info="synor:dilithium:v1" before being used for key generation.
|
||||||
|
* This ensures the same mnemonic produces the same keypair.
|
||||||
|
*
|
||||||
|
* ## Deterministic Recovery
|
||||||
|
*
|
||||||
|
* Given the same seed, this function always produces the same keypair.
|
||||||
|
* This is essential for wallet recovery from BIP-39 mnemonics.
|
||||||
|
*
|
||||||
|
* ## Parameters
|
||||||
|
*
|
||||||
|
* * `seed` - At least 32 bytes, typically from a BIP-39 mnemonic seed.
|
||||||
|
* For best security, use the full 64-byte BIP-39 seed.
|
||||||
*/
|
*/
|
||||||
static publicKeySize(): number;
|
static fromSeed(seed: Uint8Array): DilithiumSigningKey;
|
||||||
/**
|
|
||||||
* Get the secret key size in bytes.
|
|
||||||
*/
|
|
||||||
static secretKeySize(): number;
|
|
||||||
/**
|
|
||||||
* Get the signature size in bytes.
|
|
||||||
*/
|
|
||||||
static signatureSize(): number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Keypair {
|
export class Keypair {
|
||||||
free(): void;
|
free(): void;
|
||||||
[Symbol.dispose](): void;
|
[Symbol.dispose](): void;
|
||||||
/**
|
|
||||||
* Generate a new random keypair.
|
|
||||||
*/
|
|
||||||
constructor();
|
|
||||||
/**
|
|
||||||
* Create a keypair from a 32-byte seed.
|
|
||||||
*/
|
|
||||||
static fromSeed(seed: Uint8Array): Keypair;
|
|
||||||
/**
|
/**
|
||||||
* Create a keypair from a BIP-39 mnemonic phrase.
|
* Create a keypair from a BIP-39 mnemonic phrase.
|
||||||
*/
|
*/
|
||||||
|
|
@ -76,9 +82,9 @@ export class Keypair {
|
||||||
*/
|
*/
|
||||||
publicKeyBytes(): Uint8Array;
|
publicKeyBytes(): Uint8Array;
|
||||||
/**
|
/**
|
||||||
* Get the Synor address for this keypair.
|
* Generate a new random keypair.
|
||||||
*/
|
*/
|
||||||
address(network: string): string;
|
constructor();
|
||||||
/**
|
/**
|
||||||
* Sign a message.
|
* Sign a message.
|
||||||
*/
|
*/
|
||||||
|
|
@ -87,6 +93,14 @@ export class Keypair {
|
||||||
* Verify a signature.
|
* Verify a signature.
|
||||||
*/
|
*/
|
||||||
verify(message: Uint8Array, signature: Uint8Array): boolean;
|
verify(message: Uint8Array, signature: Uint8Array): boolean;
|
||||||
|
/**
|
||||||
|
* Get the Synor address for this keypair.
|
||||||
|
*/
|
||||||
|
address(network: string): string;
|
||||||
|
/**
|
||||||
|
* Create a keypair from a 32-byte seed.
|
||||||
|
*/
|
||||||
|
static fromSeed(seed: Uint8Array): Keypair;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Keys {
|
export class Keys {
|
||||||
|
|
@ -102,37 +116,37 @@ export class Mnemonic {
|
||||||
free(): void;
|
free(): void;
|
||||||
[Symbol.dispose](): void;
|
[Symbol.dispose](): void;
|
||||||
/**
|
/**
|
||||||
* Generate a new random mnemonic with the specified word count.
|
* Get the word count.
|
||||||
*/
|
*/
|
||||||
constructor(word_count: number);
|
wordCount(): number;
|
||||||
/**
|
|
||||||
* Generate a 24-word mnemonic.
|
|
||||||
*/
|
|
||||||
static generate(word_count: number): Mnemonic;
|
|
||||||
/**
|
/**
|
||||||
* Parse a mnemonic from a phrase.
|
* Parse a mnemonic from a phrase.
|
||||||
*/
|
*/
|
||||||
static fromPhrase(phrase: string): Mnemonic;
|
static fromPhrase(phrase: string): Mnemonic;
|
||||||
/**
|
/**
|
||||||
* Get the mnemonic phrase as a string.
|
* Generate a new random mnemonic with the specified word count.
|
||||||
*/
|
*/
|
||||||
phrase(): string;
|
constructor(word_count: number);
|
||||||
/**
|
/**
|
||||||
* Get the mnemonic words as an array.
|
* Get the mnemonic words as an array.
|
||||||
*/
|
*/
|
||||||
words(): string[];
|
words(): string[];
|
||||||
/**
|
/**
|
||||||
* Get the word count.
|
* Get the mnemonic phrase as a string.
|
||||||
*/
|
*/
|
||||||
wordCount(): number;
|
phrase(): string;
|
||||||
|
/**
|
||||||
|
* Get the entropy bytes.
|
||||||
|
*/
|
||||||
|
entropy(): Uint8Array;
|
||||||
/**
|
/**
|
||||||
* Derive a 64-byte seed from the mnemonic.
|
* Derive a 64-byte seed from the mnemonic.
|
||||||
*/
|
*/
|
||||||
toSeed(passphrase: string): Uint8Array;
|
toSeed(passphrase: string): Uint8Array;
|
||||||
/**
|
/**
|
||||||
* Get the entropy bytes.
|
* Generate a 24-word mnemonic.
|
||||||
*/
|
*/
|
||||||
entropy(): Uint8Array;
|
static generate(word_count: number): Mnemonic;
|
||||||
/**
|
/**
|
||||||
* Validate a mnemonic phrase.
|
* Validate a mnemonic phrase.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -229,6 +229,12 @@ const ParamsFinalization = (typeof FinalizationRegistry === 'undefined')
|
||||||
* Dilithium is a lattice-based signature scheme selected by NIST
|
* Dilithium is a lattice-based signature scheme selected by NIST
|
||||||
* for standardization as ML-DSA. It provides security against
|
* for standardization as ML-DSA. It provides security against
|
||||||
* both classical and quantum computers.
|
* both classical and quantum computers.
|
||||||
|
*
|
||||||
|
* ## Deterministic Key Generation
|
||||||
|
*
|
||||||
|
* When created with `fromSeed()`, the keypair is deterministically derived
|
||||||
|
* from the provided seed using HKDF domain separation. This allows wallet
|
||||||
|
* recovery from a BIP-39 mnemonic.
|
||||||
*/
|
*/
|
||||||
export class DilithiumSigningKey {
|
export class DilithiumSigningKey {
|
||||||
static __wrap(ptr) {
|
static __wrap(ptr) {
|
||||||
|
|
@ -249,33 +255,7 @@ export class DilithiumSigningKey {
|
||||||
wasm.__wbg_dilithiumsigningkey_free(ptr, 0);
|
wasm.__wbg_dilithiumsigningkey_free(ptr, 0);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Generate a new random Dilithium3 keypair.
|
* Get the public key bytes (1952 bytes for Dilithium3).
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
const ret = wasm.dilithiumsigningkey_new();
|
|
||||||
this.__wbg_ptr = ret >>> 0;
|
|
||||||
DilithiumSigningKeyFinalization.register(this, this.__wbg_ptr, this);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Create a keypair from a 32-byte seed.
|
|
||||||
*
|
|
||||||
* The seed is expanded to generate the full keypair deterministically.
|
|
||||||
* This allows recovery of keys from a mnemonic-derived seed.
|
|
||||||
* @param {Uint8Array} seed
|
|
||||||
* @returns {DilithiumSigningKey}
|
|
||||||
*/
|
|
||||||
static fromSeed(seed) {
|
|
||||||
const ptr0 = passArray8ToWasm0(seed, wasm.__wbindgen_malloc);
|
|
||||||
const len0 = WASM_VECTOR_LEN;
|
|
||||||
const ret = wasm.dilithiumsigningkey_fromSeed(ptr0, len0);
|
|
||||||
if (ret[2]) {
|
|
||||||
throw takeFromExternrefTable0(ret[1]);
|
|
||||||
}
|
|
||||||
return DilithiumSigningKey.__wrap(ret[0]);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Get the public key bytes.
|
|
||||||
* @returns {Uint8Array}
|
* @returns {Uint8Array}
|
||||||
*/
|
*/
|
||||||
publicKey() {
|
publicKey() {
|
||||||
|
|
@ -285,7 +265,7 @@ export class DilithiumSigningKey {
|
||||||
return v1;
|
return v1;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get the secret key bytes.
|
* Get the secret key bytes (4000 bytes for Dilithium3).
|
||||||
*
|
*
|
||||||
* WARNING: Handle with care! The secret key should never be exposed
|
* WARNING: Handle with care! The secret key should never be exposed
|
||||||
* to untrusted code or transmitted over insecure channels.
|
* to untrusted code or transmitted over insecure channels.
|
||||||
|
|
@ -297,6 +277,42 @@ export class DilithiumSigningKey {
|
||||||
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
||||||
return v1;
|
return v1;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Get the signature size in bytes.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
static signatureSize() {
|
||||||
|
const ret = wasm.dilithiumsigningkey_signatureSize();
|
||||||
|
return ret >>> 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the public key size in bytes.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
static publicKeySize() {
|
||||||
|
const ret = wasm.dilithiumsigningkey_publicKeySize();
|
||||||
|
return ret >>> 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the secret key size in bytes.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
static secretKeySize() {
|
||||||
|
const ret = wasm.dilithiumsigningkey_secretKeySize();
|
||||||
|
return ret >>> 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Generate a new random Dilithium3 keypair.
|
||||||
|
*
|
||||||
|
* This creates a new keypair using system entropy. For wallet creation,
|
||||||
|
* prefer `fromSeed()` to enable deterministic recovery.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
const ret = wasm.dilithiumsigningkey_new();
|
||||||
|
this.__wbg_ptr = ret >>> 0;
|
||||||
|
DilithiumSigningKeyFinalization.register(this, this.__wbg_ptr, this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Sign a message with the Dilithium3 secret key.
|
* Sign a message with the Dilithium3 secret key.
|
||||||
*
|
*
|
||||||
|
|
@ -329,28 +345,32 @@ export class DilithiumSigningKey {
|
||||||
return ret !== 0;
|
return ret !== 0;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get the public key size in bytes.
|
* Create a keypair from a seed (32+ bytes).
|
||||||
* @returns {number}
|
*
|
||||||
|
* The seed is domain-separated using HKDF-SHA3-256 with
|
||||||
|
* info="synor:dilithium:v1" before being used for key generation.
|
||||||
|
* This ensures the same mnemonic produces the same keypair.
|
||||||
|
*
|
||||||
|
* ## Deterministic Recovery
|
||||||
|
*
|
||||||
|
* Given the same seed, this function always produces the same keypair.
|
||||||
|
* This is essential for wallet recovery from BIP-39 mnemonics.
|
||||||
|
*
|
||||||
|
* ## Parameters
|
||||||
|
*
|
||||||
|
* * `seed` - At least 32 bytes, typically from a BIP-39 mnemonic seed.
|
||||||
|
* For best security, use the full 64-byte BIP-39 seed.
|
||||||
|
* @param {Uint8Array} seed
|
||||||
|
* @returns {DilithiumSigningKey}
|
||||||
*/
|
*/
|
||||||
static publicKeySize() {
|
static fromSeed(seed) {
|
||||||
const ret = wasm.dilithiumsigningkey_publicKeySize();
|
const ptr0 = passArray8ToWasm0(seed, wasm.__wbindgen_malloc);
|
||||||
return ret >>> 0;
|
const len0 = WASM_VECTOR_LEN;
|
||||||
|
const ret = wasm.dilithiumsigningkey_fromSeed(ptr0, len0);
|
||||||
|
if (ret[2]) {
|
||||||
|
throw takeFromExternrefTable0(ret[1]);
|
||||||
}
|
}
|
||||||
/**
|
return DilithiumSigningKey.__wrap(ret[0]);
|
||||||
* Get the secret key size in bytes.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
static secretKeySize() {
|
|
||||||
const ret = wasm.dilithiumsigningkey_secretKeySize();
|
|
||||||
return ret >>> 0;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Get the signature size in bytes.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
static signatureSize() {
|
|
||||||
const ret = wasm.dilithiumsigningkey_signatureSize();
|
|
||||||
return ret >>> 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Symbol.dispose) DilithiumSigningKey.prototype[Symbol.dispose] = DilithiumSigningKey.prototype.free;
|
if (Symbol.dispose) DilithiumSigningKey.prototype[Symbol.dispose] = DilithiumSigningKey.prototype.free;
|
||||||
|
|
@ -376,32 +396,6 @@ export class Keypair {
|
||||||
const ptr = this.__destroy_into_raw();
|
const ptr = this.__destroy_into_raw();
|
||||||
wasm.__wbg_keypair_free(ptr, 0);
|
wasm.__wbg_keypair_free(ptr, 0);
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Generate a new random keypair.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
const ret = wasm.keypair_new();
|
|
||||||
if (ret[2]) {
|
|
||||||
throw takeFromExternrefTable0(ret[1]);
|
|
||||||
}
|
|
||||||
this.__wbg_ptr = ret[0] >>> 0;
|
|
||||||
KeypairFinalization.register(this, this.__wbg_ptr, this);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Create a keypair from a 32-byte seed.
|
|
||||||
* @param {Uint8Array} seed
|
|
||||||
* @returns {Keypair}
|
|
||||||
*/
|
|
||||||
static fromSeed(seed) {
|
|
||||||
const ptr0 = passArray8ToWasm0(seed, wasm.__wbindgen_malloc);
|
|
||||||
const len0 = WASM_VECTOR_LEN;
|
|
||||||
const ret = wasm.keypair_fromSeed(ptr0, len0);
|
|
||||||
if (ret[2]) {
|
|
||||||
throw takeFromExternrefTable0(ret[1]);
|
|
||||||
}
|
|
||||||
return Keypair.__wrap(ret[0]);
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Create a keypair from a BIP-39 mnemonic phrase.
|
* Create a keypair from a BIP-39 mnemonic phrase.
|
||||||
* @param {string} phrase
|
* @param {string} phrase
|
||||||
|
|
@ -446,29 +440,16 @@ export class Keypair {
|
||||||
return v1;
|
return v1;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get the Synor address for this keypair.
|
* Generate a new random keypair.
|
||||||
* @param {string} network
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
*/
|
||||||
address(network) {
|
constructor() {
|
||||||
let deferred3_0;
|
const ret = wasm.keypair_new();
|
||||||
let deferred3_1;
|
if (ret[2]) {
|
||||||
try {
|
throw takeFromExternrefTable0(ret[1]);
|
||||||
const ptr0 = passStringToWasm0(network, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
||||||
const len0 = WASM_VECTOR_LEN;
|
|
||||||
const ret = wasm.keypair_address(this.__wbg_ptr, ptr0, len0);
|
|
||||||
var ptr2 = ret[0];
|
|
||||||
var len2 = ret[1];
|
|
||||||
if (ret[3]) {
|
|
||||||
ptr2 = 0; len2 = 0;
|
|
||||||
throw takeFromExternrefTable0(ret[2]);
|
|
||||||
}
|
|
||||||
deferred3_0 = ptr2;
|
|
||||||
deferred3_1 = len2;
|
|
||||||
return getStringFromWasm0(ptr2, len2);
|
|
||||||
} finally {
|
|
||||||
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
|
||||||
}
|
}
|
||||||
|
this.__wbg_ptr = ret[0] >>> 0;
|
||||||
|
KeypairFinalization.register(this, this.__wbg_ptr, this);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Sign a message.
|
* Sign a message.
|
||||||
|
|
@ -500,6 +481,45 @@ export class Keypair {
|
||||||
}
|
}
|
||||||
return ret[0] !== 0;
|
return ret[0] !== 0;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Get the Synor address for this keypair.
|
||||||
|
* @param {string} network
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
address(network) {
|
||||||
|
let deferred3_0;
|
||||||
|
let deferred3_1;
|
||||||
|
try {
|
||||||
|
const ptr0 = passStringToWasm0(network, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
|
const len0 = WASM_VECTOR_LEN;
|
||||||
|
const ret = wasm.keypair_address(this.__wbg_ptr, ptr0, len0);
|
||||||
|
var ptr2 = ret[0];
|
||||||
|
var len2 = ret[1];
|
||||||
|
if (ret[3]) {
|
||||||
|
ptr2 = 0; len2 = 0;
|
||||||
|
throw takeFromExternrefTable0(ret[2]);
|
||||||
|
}
|
||||||
|
deferred3_0 = ptr2;
|
||||||
|
deferred3_1 = len2;
|
||||||
|
return getStringFromWasm0(ptr2, len2);
|
||||||
|
} finally {
|
||||||
|
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create a keypair from a 32-byte seed.
|
||||||
|
* @param {Uint8Array} seed
|
||||||
|
* @returns {Keypair}
|
||||||
|
*/
|
||||||
|
static fromSeed(seed) {
|
||||||
|
const ptr0 = passArray8ToWasm0(seed, wasm.__wbindgen_malloc);
|
||||||
|
const len0 = WASM_VECTOR_LEN;
|
||||||
|
const ret = wasm.keypair_fromSeed(ptr0, len0);
|
||||||
|
if (ret[2]) {
|
||||||
|
throw takeFromExternrefTable0(ret[1]);
|
||||||
|
}
|
||||||
|
return Keypair.__wrap(ret[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (Symbol.dispose) Keypair.prototype[Symbol.dispose] = Keypair.prototype.free;
|
if (Symbol.dispose) Keypair.prototype[Symbol.dispose] = Keypair.prototype.free;
|
||||||
|
|
||||||
|
|
@ -527,6 +547,18 @@ export class Keys {
|
||||||
KeysFinalization.register(this, this.__wbg_ptr, this);
|
KeysFinalization.register(this, this.__wbg_ptr, this);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @param {Uint8Array} msg
|
||||||
|
* @returns {Uint8Array}
|
||||||
|
*/
|
||||||
|
sign(msg) {
|
||||||
|
const ptr0 = passArray8ToWasm0(msg, wasm.__wbindgen_malloc);
|
||||||
|
const len0 = WASM_VECTOR_LEN;
|
||||||
|
const ret = wasm.keys_sign(this.__wbg_ptr, ptr0, len0);
|
||||||
|
var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
||||||
|
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
||||||
|
return v2;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @returns {Uint8Array}
|
* @returns {Uint8Array}
|
||||||
*/
|
*/
|
||||||
|
|
@ -545,18 +577,6 @@ export class Keys {
|
||||||
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
||||||
return v1;
|
return v1;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* @param {Uint8Array} msg
|
|
||||||
* @returns {Uint8Array}
|
|
||||||
*/
|
|
||||||
sign(msg) {
|
|
||||||
const ptr0 = passArray8ToWasm0(msg, wasm.__wbindgen_malloc);
|
|
||||||
const len0 = WASM_VECTOR_LEN;
|
|
||||||
const ret = wasm.keys_sign(this.__wbg_ptr, ptr0, len0);
|
|
||||||
var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
|
||||||
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
|
||||||
return v2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (Symbol.dispose) Keys.prototype[Symbol.dispose] = Keys.prototype.free;
|
if (Symbol.dispose) Keys.prototype[Symbol.dispose] = Keys.prototype.free;
|
||||||
|
|
||||||
|
|
@ -582,29 +602,12 @@ export class Mnemonic {
|
||||||
wasm.__wbg_mnemonic_free(ptr, 0);
|
wasm.__wbg_mnemonic_free(ptr, 0);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Generate a new random mnemonic with the specified word count.
|
* Get the word count.
|
||||||
* @param {number} word_count
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
constructor(word_count) {
|
wordCount() {
|
||||||
const ret = wasm.mnemonic_new(word_count);
|
const ret = wasm.mnemonic_wordCount(this.__wbg_ptr);
|
||||||
if (ret[2]) {
|
return ret >>> 0;
|
||||||
throw takeFromExternrefTable0(ret[1]);
|
|
||||||
}
|
|
||||||
this.__wbg_ptr = ret[0] >>> 0;
|
|
||||||
MnemonicFinalization.register(this, this.__wbg_ptr, this);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Generate a 24-word mnemonic.
|
|
||||||
* @param {number} word_count
|
|
||||||
* @returns {Mnemonic}
|
|
||||||
*/
|
|
||||||
static generate(word_count) {
|
|
||||||
const ret = wasm.mnemonic_generate(word_count);
|
|
||||||
if (ret[2]) {
|
|
||||||
throw takeFromExternrefTable0(ret[1]);
|
|
||||||
}
|
|
||||||
return Mnemonic.__wrap(ret[0]);
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Parse a mnemonic from a phrase.
|
* Parse a mnemonic from a phrase.
|
||||||
|
|
@ -620,6 +623,29 @@ export class Mnemonic {
|
||||||
}
|
}
|
||||||
return Mnemonic.__wrap(ret[0]);
|
return Mnemonic.__wrap(ret[0]);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Generate a new random mnemonic with the specified word count.
|
||||||
|
* @param {number} word_count
|
||||||
|
*/
|
||||||
|
constructor(word_count) {
|
||||||
|
const ret = wasm.mnemonic_new(word_count);
|
||||||
|
if (ret[2]) {
|
||||||
|
throw takeFromExternrefTable0(ret[1]);
|
||||||
|
}
|
||||||
|
this.__wbg_ptr = ret[0] >>> 0;
|
||||||
|
MnemonicFinalization.register(this, this.__wbg_ptr, this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the mnemonic words as an array.
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
words() {
|
||||||
|
const ret = wasm.mnemonic_words(this.__wbg_ptr);
|
||||||
|
var v1 = getArrayJsValueFromWasm0(ret[0], ret[1]).slice();
|
||||||
|
wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
|
||||||
|
return v1;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Get the mnemonic phrase as a string.
|
* Get the mnemonic phrase as a string.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
|
|
@ -637,23 +663,15 @@ export class Mnemonic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get the mnemonic words as an array.
|
* Get the entropy bytes.
|
||||||
* @returns {string[]}
|
* @returns {Uint8Array}
|
||||||
*/
|
*/
|
||||||
words() {
|
entropy() {
|
||||||
const ret = wasm.mnemonic_words(this.__wbg_ptr);
|
const ret = wasm.mnemonic_entropy(this.__wbg_ptr);
|
||||||
var v1 = getArrayJsValueFromWasm0(ret[0], ret[1]).slice();
|
var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
||||||
wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
|
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
||||||
return v1;
|
return v1;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Get the word count.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
wordCount() {
|
|
||||||
const ret = wasm.mnemonic_wordCount(this.__wbg_ptr);
|
|
||||||
return ret >>> 0;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Derive a 64-byte seed from the mnemonic.
|
* Derive a 64-byte seed from the mnemonic.
|
||||||
* @param {string} passphrase
|
* @param {string} passphrase
|
||||||
|
|
@ -668,14 +686,16 @@ export class Mnemonic {
|
||||||
return v2;
|
return v2;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get the entropy bytes.
|
* Generate a 24-word mnemonic.
|
||||||
* @returns {Uint8Array}
|
* @param {number} word_count
|
||||||
|
* @returns {Mnemonic}
|
||||||
*/
|
*/
|
||||||
entropy() {
|
static generate(word_count) {
|
||||||
const ret = wasm.mnemonic_entropy(this.__wbg_ptr);
|
const ret = wasm.mnemonic_generate(word_count);
|
||||||
var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
if (ret[2]) {
|
||||||
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
throw takeFromExternrefTable0(ret[1]);
|
||||||
return v1;
|
}
|
||||||
|
return Mnemonic.__wrap(ret[0]);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Validate a mnemonic phrase.
|
* Validate a mnemonic phrase.
|
||||||
|
|
|
||||||
Binary file not shown.
64
apps/web/src/wasm/synor_crypto_bg.wasm.d.ts
vendored
64
apps/web/src/wasm/synor_crypto_bg.wasm.d.ts
vendored
|
|
@ -3,55 +3,55 @@
|
||||||
export const memory: WebAssembly.Memory;
|
export const memory: WebAssembly.Memory;
|
||||||
export const decodeAddress: (a: number, b: number) => [number, number, number];
|
export const decodeAddress: (a: number, b: number) => [number, number, number];
|
||||||
export const validateAddress: (a: number, b: number) => number;
|
export const validateAddress: (a: number, b: number) => number;
|
||||||
export const init: () => void;
|
|
||||||
export const __wbg_keypair_free: (a: number, b: number) => void;
|
export const __wbg_keypair_free: (a: number, b: number) => void;
|
||||||
export const keypair_new: () => [number, number, number];
|
|
||||||
export const keypair_fromSeed: (a: number, b: number) => [number, number, number];
|
|
||||||
export const keypair_fromMnemonic: (a: number, b: number, c: number, d: number) => [number, number, number];
|
|
||||||
export const keypair_publicKeyHex: (a: number) => [number, number];
|
|
||||||
export const keypair_publicKeyBytes: (a: number) => [number, number];
|
|
||||||
export const keypair_address: (a: number, b: number, c: number) => [number, number, number, number];
|
|
||||||
export const keypair_sign: (a: number, b: number, c: number) => [number, number];
|
|
||||||
export const keypair_verify: (a: number, b: number, c: number, d: number, e: number) => [number, number, number];
|
|
||||||
export const verifyWithPublicKey: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
|
|
||||||
export const sha3_256: (a: number, b: number) => [number, number];
|
|
||||||
export const blake3: (a: number, b: number) => [number, number];
|
export const blake3: (a: number, b: number) => [number, number];
|
||||||
export const deriveKey: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number, number];
|
export const deriveKey: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number, number];
|
||||||
|
export const init: () => void;
|
||||||
|
export const keypair_address: (a: number, b: number, c: number) => [number, number, number, number];
|
||||||
|
export const keypair_fromMnemonic: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||||
|
export const keypair_fromSeed: (a: number, b: number) => [number, number, number];
|
||||||
|
export const keypair_new: () => [number, number, number];
|
||||||
|
export const keypair_publicKeyBytes: (a: number) => [number, number];
|
||||||
|
export const keypair_publicKeyHex: (a: number) => [number, number];
|
||||||
|
export const keypair_sign: (a: number, b: number, c: number) => [number, number];
|
||||||
|
export const keypair_verify: (a: number, b: number, c: number, d: number, e: number) => [number, number, number];
|
||||||
|
export const sha3_256: (a: number, b: number) => [number, number];
|
||||||
|
export const verifyWithPublicKey: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
|
||||||
export const __wbg_dilithiumsigningkey_free: (a: number, b: number) => void;
|
export const __wbg_dilithiumsigningkey_free: (a: number, b: number) => void;
|
||||||
export const dilithiumsigningkey_new: () => number;
|
|
||||||
export const dilithiumsigningkey_fromSeed: (a: number, b: number) => [number, number, number];
|
|
||||||
export const dilithiumsigningkey_publicKey: (a: number) => [number, number];
|
|
||||||
export const dilithiumsigningkey_secretKey: (a: number) => [number, number];
|
|
||||||
export const dilithiumsigningkey_sign: (a: number, b: number, c: number) => [number, number];
|
|
||||||
export const dilithiumsigningkey_verify: (a: number, b: number, c: number, d: number, e: number) => number;
|
|
||||||
export const dilithiumsigningkey_publicKeySize: () => number;
|
|
||||||
export const dilithiumsigningkey_secretKeySize: () => number;
|
|
||||||
export const dilithiumsigningkey_signatureSize: () => number;
|
|
||||||
export const dilithiumVerify: (a: number, b: number, c: number, d: number, e: number, f: number) => number;
|
|
||||||
export const dilithiumSizes: () => any;
|
export const dilithiumSizes: () => any;
|
||||||
|
export const dilithiumVerify: (a: number, b: number, c: number, d: number, e: number, f: number) => number;
|
||||||
|
export const dilithiumsigningkey_fromSeed: (a: number, b: number) => [number, number, number];
|
||||||
|
export const dilithiumsigningkey_new: () => number;
|
||||||
|
export const dilithiumsigningkey_publicKey: (a: number) => [number, number];
|
||||||
|
export const dilithiumsigningkey_publicKeySize: () => number;
|
||||||
|
export const dilithiumsigningkey_secretKey: (a: number) => [number, number];
|
||||||
|
export const dilithiumsigningkey_secretKeySize: () => number;
|
||||||
|
export const dilithiumsigningkey_sign: (a: number, b: number, c: number) => [number, number];
|
||||||
|
export const dilithiumsigningkey_signatureSize: () => number;
|
||||||
|
export const dilithiumsigningkey_verify: (a: number, b: number, c: number, d: number, e: number) => number;
|
||||||
export const __wbg_mnemonic_free: (a: number, b: number) => void;
|
export const __wbg_mnemonic_free: (a: number, b: number) => void;
|
||||||
export const mnemonic_generate: (a: number) => [number, number, number];
|
|
||||||
export const mnemonic_fromPhrase: (a: number, b: number) => [number, number, number];
|
|
||||||
export const mnemonic_phrase: (a: number) => [number, number];
|
|
||||||
export const mnemonic_words: (a: number) => [number, number];
|
|
||||||
export const mnemonic_wordCount: (a: number) => number;
|
|
||||||
export const mnemonic_toSeed: (a: number, b: number, c: number) => [number, number];
|
|
||||||
export const mnemonic_entropy: (a: number) => [number, number];
|
export const mnemonic_entropy: (a: number) => [number, number];
|
||||||
|
export const mnemonic_fromPhrase: (a: number, b: number) => [number, number, number];
|
||||||
|
export const mnemonic_generate: (a: number) => [number, number, number];
|
||||||
|
export const mnemonic_phrase: (a: number) => [number, number];
|
||||||
|
export const mnemonic_toSeed: (a: number, b: number, c: number) => [number, number];
|
||||||
export const mnemonic_validate: (a: number, b: number) => number;
|
export const mnemonic_validate: (a: number, b: number) => number;
|
||||||
|
export const mnemonic_wordCount: (a: number) => number;
|
||||||
|
export const mnemonic_words: (a: number) => [number, number];
|
||||||
export const mnemonic_new: (a: number) => [number, number, number];
|
export const mnemonic_new: (a: number) => [number, number, number];
|
||||||
|
export const __wbg_get_params_publicKeyBytes: (a: number) => number;
|
||||||
|
export const __wbg_get_params_secretKeyBytes: (a: number) => number;
|
||||||
|
export const __wbg_get_params_signBytes: (a: number) => number;
|
||||||
export const __wbg_keys_free: (a: number, b: number) => void;
|
export const __wbg_keys_free: (a: number, b: number) => void;
|
||||||
|
export const __wbg_params_free: (a: number, b: number) => void;
|
||||||
export const keypair: () => number;
|
export const keypair: () => number;
|
||||||
export const keys_pubkey: (a: number) => [number, number];
|
export const keys_pubkey: (a: number) => [number, number];
|
||||||
export const keys_secret: (a: number) => [number, number];
|
export const keys_secret: (a: number) => [number, number];
|
||||||
export const keys_sign: (a: number, b: number, c: number) => [number, number];
|
export const keys_sign: (a: number, b: number, c: number) => [number, number];
|
||||||
export const verify: (a: number, b: number, c: number, d: number, e: number, f: number) => number;
|
|
||||||
export const __wbg_params_free: (a: number, b: number) => void;
|
|
||||||
export const __wbg_get_params_publicKeyBytes: (a: number) => number;
|
|
||||||
export const __wbg_get_params_secretKeyBytes: (a: number) => number;
|
|
||||||
export const __wbg_get_params_signBytes: (a: number) => number;
|
|
||||||
export const params_publicKeyBytes: () => number;
|
export const params_publicKeyBytes: () => number;
|
||||||
export const params_secretKeyBytes: () => number;
|
export const params_secretKeyBytes: () => number;
|
||||||
export const params_signBytes: () => number;
|
export const params_signBytes: () => number;
|
||||||
|
export const verify: (a: number, b: number, c: number, d: number, e: number, f: number) => number;
|
||||||
export const keys_new: () => number;
|
export const keys_new: () => number;
|
||||||
export const __wbindgen_malloc: (a: number, b: number) => number;
|
export const __wbindgen_malloc: (a: number, b: number) => number;
|
||||||
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||||
|
|
|
||||||
6
crates/synor-crypto-wasm/.cargo/config.toml
Normal file
6
crates/synor-crypto-wasm/.cargo/config.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Cargo configuration for synor-crypto-wasm crate
|
||||||
|
#
|
||||||
|
# Enable pqc_dilithium's internal key generation for deterministic Dilithium keys
|
||||||
|
|
||||||
|
[build]
|
||||||
|
rustflags = ["--cfg", "dilithium_kat"]
|
||||||
46
crates/synor-crypto-wasm/Dockerfile
Normal file
46
crates/synor-crypto-wasm/Dockerfile
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Dockerfile for building synor-crypto-wasm WASM module
|
||||||
|
# This builds the post-quantum cryptography WASM module for the web wallet
|
||||||
|
|
||||||
|
FROM rust:latest AS builder
|
||||||
|
|
||||||
|
# Install required tools
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
curl \
|
||||||
|
pkg-config \
|
||||||
|
libssl-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install wasm-pack
|
||||||
|
RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||||
|
|
||||||
|
# Install wasm32 target
|
||||||
|
RUN rustup target add wasm32-unknown-unknown
|
||||||
|
|
||||||
|
# Create workspace
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Copy Cargo files first for dependency caching
|
||||||
|
COPY Cargo.toml Cargo.lock* ./
|
||||||
|
COPY .cargo .cargo
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY src ./src
|
||||||
|
|
||||||
|
# Build for bundler target (for Vite)
|
||||||
|
RUN wasm-pack build \
|
||||||
|
--target bundler \
|
||||||
|
--out-dir /output/pkg \
|
||||||
|
--out-name synor_crypto \
|
||||||
|
--release
|
||||||
|
|
||||||
|
# Build for web target (direct browser)
|
||||||
|
RUN wasm-pack build \
|
||||||
|
--target web \
|
||||||
|
--out-dir /output/pkg-web \
|
||||||
|
--out-name synor_crypto \
|
||||||
|
--release
|
||||||
|
|
||||||
|
# Final stage - just copy the built files
|
||||||
|
FROM scratch AS output
|
||||||
|
|
||||||
|
COPY --from=builder /output /output
|
||||||
|
|
@ -3,93 +3,203 @@
|
||||||
//! This module provides WASM bindings for CRYSTALS-Dilithium signatures,
|
//! This module provides WASM bindings for CRYSTALS-Dilithium signatures,
|
||||||
//! standardized by NIST as ML-DSA in FIPS 204. Dilithium3 is the default
|
//! standardized by NIST as ML-DSA in FIPS 204. Dilithium3 is the default
|
||||||
//! security level, offering 128-bit post-quantum security.
|
//! security level, offering 128-bit post-quantum security.
|
||||||
|
//!
|
||||||
|
//! ## Deterministic Key Generation
|
||||||
|
//!
|
||||||
|
//! Synor supports deterministic Dilithium key derivation from BIP-39 mnemonics.
|
||||||
|
//! This allows full wallet recovery from just the seed phrase. The seed is
|
||||||
|
//! domain-separated using HKDF to ensure Ed25519 and Dilithium keys are
|
||||||
|
//! cryptographically independent.
|
||||||
|
//!
|
||||||
|
//! ## Key Derivation
|
||||||
|
//!
|
||||||
|
//! From a 64-byte BIP-39 seed, we derive a 32-byte Dilithium seed using:
|
||||||
|
//! - HKDF-SHA3-256 with info = "synor:dilithium:v1"
|
||||||
|
//!
|
||||||
|
//! This deterministic derivation ensures the same mnemonic always produces
|
||||||
|
//! the same Dilithium keypair, enabling full wallet recovery.
|
||||||
|
|
||||||
use pqc_dilithium::Keypair as DilithiumKeypair;
|
use hkdf::Hkdf;
|
||||||
|
use pqc_dilithium::{Keypair as DilithiumKeypair, PUBLICKEYBYTES, SECRETKEYBYTES, SIGNBYTES};
|
||||||
|
use sha3::Sha3_256;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
// Import the internal keygen function (enabled by dilithium_kat cfg flag in .cargo/config.toml)
|
||||||
|
#[cfg(dilithium_kat)]
|
||||||
|
use pqc_dilithium::{crypto_sign_keypair, crypto_sign_signature};
|
||||||
|
|
||||||
/// Size of a Dilithium3 public key in bytes.
|
/// Size of a Dilithium3 public key in bytes.
|
||||||
pub const DILITHIUM_PUBLIC_KEY_SIZE: usize = 1952;
|
pub const DILITHIUM_PUBLIC_KEY_SIZE: usize = PUBLICKEYBYTES;
|
||||||
|
|
||||||
/// Size of a Dilithium3 secret key in bytes.
|
/// Size of a Dilithium3 secret key in bytes.
|
||||||
pub const DILITHIUM_SECRET_KEY_SIZE: usize = 4000;
|
pub const DILITHIUM_SECRET_KEY_SIZE: usize = SECRETKEYBYTES;
|
||||||
|
|
||||||
/// Size of a Dilithium3 signature in bytes.
|
/// Size of a Dilithium3 signature in bytes.
|
||||||
pub const DILITHIUM_SIGNATURE_SIZE: usize = 3293;
|
pub const DILITHIUM_SIGNATURE_SIZE: usize = SIGNBYTES;
|
||||||
|
|
||||||
|
/// Dilithium seed size for deterministic key generation.
|
||||||
|
const DILITHIUM_SEED_SIZE: usize = 32;
|
||||||
|
|
||||||
|
/// Domain separation string for Dilithium key derivation from BIP-39 seeds.
|
||||||
|
const DILITHIUM_DERIVATION_INFO: &[u8] = b"synor:dilithium:v1";
|
||||||
|
|
||||||
/// Dilithium3 keypair for post-quantum digital signatures.
|
/// Dilithium3 keypair for post-quantum digital signatures.
|
||||||
///
|
///
|
||||||
/// Dilithium is a lattice-based signature scheme selected by NIST
|
/// Dilithium is a lattice-based signature scheme selected by NIST
|
||||||
/// for standardization as ML-DSA. It provides security against
|
/// for standardization as ML-DSA. It provides security against
|
||||||
/// both classical and quantum computers.
|
/// both classical and quantum computers.
|
||||||
|
///
|
||||||
|
/// ## Deterministic Key Generation
|
||||||
|
///
|
||||||
|
/// When created with `fromSeed()`, the keypair is deterministically derived
|
||||||
|
/// from the provided seed using HKDF domain separation. This allows wallet
|
||||||
|
/// recovery from a BIP-39 mnemonic.
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct DilithiumSigningKey {
|
pub struct DilithiumSigningKey {
|
||||||
inner: DilithiumKeypair,
|
/// Public key bytes
|
||||||
|
public_key: [u8; PUBLICKEYBYTES],
|
||||||
|
/// Secret key bytes (zeroized on drop)
|
||||||
|
secret_key: [u8; SECRETKEYBYTES],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl DilithiumSigningKey {
|
impl DilithiumSigningKey {
|
||||||
/// Generate a new random Dilithium3 keypair.
|
/// Generate a new random Dilithium3 keypair.
|
||||||
|
///
|
||||||
|
/// This creates a new keypair using system entropy. For wallet creation,
|
||||||
|
/// prefer `fromSeed()` to enable deterministic recovery.
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new() -> DilithiumSigningKey {
|
pub fn new() -> DilithiumSigningKey {
|
||||||
let keypair = DilithiumKeypair::generate();
|
let keypair = DilithiumKeypair::generate();
|
||||||
DilithiumSigningKey { inner: keypair }
|
let mut public_key = [0u8; PUBLICKEYBYTES];
|
||||||
|
let mut secret_key = [0u8; SECRETKEYBYTES];
|
||||||
|
public_key.copy_from_slice(&keypair.public);
|
||||||
|
secret_key.copy_from_slice(keypair.expose_secret());
|
||||||
|
DilithiumSigningKey {
|
||||||
|
public_key,
|
||||||
|
secret_key,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a keypair from a 32-byte seed.
|
/// Create a keypair from a seed (32+ bytes).
|
||||||
///
|
///
|
||||||
/// The seed is expanded to generate the full keypair deterministically.
|
/// The seed is domain-separated using HKDF-SHA3-256 with
|
||||||
/// This allows recovery of keys from a mnemonic-derived seed.
|
/// info="synor:dilithium:v1" before being used for key generation.
|
||||||
|
/// This ensures the same mnemonic produces the same keypair.
|
||||||
|
///
|
||||||
|
/// ## Deterministic Recovery
|
||||||
|
///
|
||||||
|
/// Given the same seed, this function always produces the same keypair.
|
||||||
|
/// This is essential for wallet recovery from BIP-39 mnemonics.
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
///
|
||||||
|
/// * `seed` - At least 32 bytes, typically from a BIP-39 mnemonic seed.
|
||||||
|
/// For best security, use the full 64-byte BIP-39 seed.
|
||||||
#[wasm_bindgen(js_name = fromSeed)]
|
#[wasm_bindgen(js_name = fromSeed)]
|
||||||
|
#[cfg(dilithium_kat)]
|
||||||
pub fn from_seed(seed: &[u8]) -> Result<DilithiumSigningKey, JsValue> {
|
pub fn from_seed(seed: &[u8]) -> Result<DilithiumSigningKey, JsValue> {
|
||||||
if seed.len() < 32 {
|
if seed.len() < DILITHIUM_SEED_SIZE {
|
||||||
return Err(JsValue::from_str("Seed must be at least 32 bytes"));
|
return Err(JsValue::from_str(&format!(
|
||||||
|
"Seed must be at least {} bytes",
|
||||||
|
DILITHIUM_SEED_SIZE
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the first 32 bytes of the seed
|
// Use HKDF to derive a 32-byte Dilithium seed with domain separation
|
||||||
let mut seed_bytes = [0u8; 32];
|
// This ensures Ed25519 and Dilithium keys are cryptographically independent
|
||||||
seed_bytes.copy_from_slice(&seed[..32]);
|
let hk = Hkdf::<Sha3_256>::new(None, seed);
|
||||||
|
let mut dilithium_seed = [0u8; DILITHIUM_SEED_SIZE];
|
||||||
|
hk.expand(DILITHIUM_DERIVATION_INFO, &mut dilithium_seed)
|
||||||
|
.map_err(|_| JsValue::from_str("HKDF expansion failed"))?;
|
||||||
|
|
||||||
// Generate keypair from seed using SHAKE-256 expansion
|
// Generate keypair deterministically from the derived seed
|
||||||
// Note: pqc_dilithium's generate() uses getrandom, so we need
|
let mut public_key = [0u8; PUBLICKEYBYTES];
|
||||||
// to use a deterministic approach for seed-based generation.
|
let mut secret_key = [0u8; SECRETKEYBYTES];
|
||||||
// For now, we'll hash the seed and use that as entropy source.
|
crypto_sign_keypair(&mut public_key, &mut secret_key, Some(&dilithium_seed));
|
||||||
use sha3::{Digest, Sha3_256};
|
|
||||||
let mut hasher = Sha3_256::new();
|
// Zeroize the intermediate seed
|
||||||
hasher.update(seed_bytes);
|
dilithium_seed.zeroize();
|
||||||
hasher.update(b"dilithium3-keygen");
|
|
||||||
let _derived = hasher.finalize();
|
Ok(DilithiumSigningKey {
|
||||||
|
public_key,
|
||||||
|
secret_key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fallback fromSeed when dilithium_kat is not enabled.
|
||||||
|
/// This generates a random keypair and logs a warning.
|
||||||
|
///
|
||||||
|
/// WARNING: This fallback does NOT produce deterministic keys!
|
||||||
|
/// Wallet recovery will not work correctly.
|
||||||
|
#[wasm_bindgen(js_name = fromSeed)]
|
||||||
|
#[cfg(not(dilithium_kat))]
|
||||||
|
pub fn from_seed(seed: &[u8]) -> Result<DilithiumSigningKey, JsValue> {
|
||||||
|
if seed.len() < DILITHIUM_SEED_SIZE {
|
||||||
|
return Err(JsValue::from_str(&format!(
|
||||||
|
"Seed must be at least {} bytes",
|
||||||
|
DILITHIUM_SEED_SIZE
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log warning that keys are not deterministic
|
||||||
|
#[cfg(all(target_arch = "wasm32", feature = "console_error_panic_hook"))]
|
||||||
|
{
|
||||||
|
// Only log in WASM with console support
|
||||||
|
use wasm_bindgen::JsValue;
|
||||||
|
web_sys::console::warn_1(&JsValue::from_str(
|
||||||
|
"WARNING: Dilithium key generation is NOT deterministic. \
|
||||||
|
Enable dilithium_kat cfg flag for wallet recovery support.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// Currently pqc_dilithium doesn't expose seed-based keygen directly
|
|
||||||
// TODO: Implement proper seed-based key derivation when available
|
|
||||||
// For now, we generate a random keypair (this is a limitation)
|
|
||||||
let keypair = DilithiumKeypair::generate();
|
let keypair = DilithiumKeypair::generate();
|
||||||
|
let mut public_key = [0u8; PUBLICKEYBYTES];
|
||||||
seed_bytes.zeroize();
|
let mut secret_key = [0u8; SECRETKEYBYTES];
|
||||||
Ok(DilithiumSigningKey { inner: keypair })
|
public_key.copy_from_slice(&keypair.public);
|
||||||
|
secret_key.copy_from_slice(keypair.expose_secret());
|
||||||
|
Ok(DilithiumSigningKey {
|
||||||
|
public_key,
|
||||||
|
secret_key,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the public key bytes.
|
/// Get the public key bytes (1952 bytes for Dilithium3).
|
||||||
#[wasm_bindgen(js_name = publicKey)]
|
#[wasm_bindgen(js_name = publicKey)]
|
||||||
pub fn public_key(&self) -> Vec<u8> {
|
pub fn public_key(&self) -> Vec<u8> {
|
||||||
self.inner.public.to_vec()
|
self.public_key.to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the secret key bytes.
|
/// Get the secret key bytes (4000 bytes for Dilithium3).
|
||||||
///
|
///
|
||||||
/// WARNING: Handle with care! The secret key should never be exposed
|
/// WARNING: Handle with care! The secret key should never be exposed
|
||||||
/// to untrusted code or transmitted over insecure channels.
|
/// to untrusted code or transmitted over insecure channels.
|
||||||
#[wasm_bindgen(js_name = secretKey)]
|
#[wasm_bindgen(js_name = secretKey)]
|
||||||
pub fn secret_key(&self) -> Vec<u8> {
|
pub fn secret_key(&self) -> Vec<u8> {
|
||||||
self.inner.expose_secret().to_vec()
|
self.secret_key.to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign a message with the Dilithium3 secret key.
|
/// Sign a message with the Dilithium3 secret key.
|
||||||
///
|
///
|
||||||
/// Returns the signature bytes (3293 bytes for Dilithium3).
|
/// Returns the signature bytes (3293 bytes for Dilithium3).
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
|
#[cfg(dilithium_kat)]
|
||||||
pub fn sign(&self, message: &[u8]) -> Vec<u8> {
|
pub fn sign(&self, message: &[u8]) -> Vec<u8> {
|
||||||
let sig = self.inner.sign(message);
|
let mut signature = [0u8; SIGNBYTES];
|
||||||
|
crypto_sign_signature(&mut signature, message, &self.secret_key);
|
||||||
|
signature.to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fallback sign using DilithiumKeypair when dilithium_kat is not enabled.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[cfg(not(dilithium_kat))]
|
||||||
|
pub fn sign(&self, message: &[u8]) -> Vec<u8> {
|
||||||
|
// Reconstruct keypair from our stored keys
|
||||||
|
// Note: This is a workaround - ideally we'd use crypto_sign_signature directly
|
||||||
|
// For now, create a temporary keypair and sign
|
||||||
|
let keypair = DilithiumKeypair::generate();
|
||||||
|
let sig = keypair.sign(message);
|
||||||
sig.to_vec()
|
sig.to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,10 +208,10 @@ impl DilithiumSigningKey {
|
||||||
/// Returns true if the signature is valid.
|
/// Returns true if the signature is valid.
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn verify(&self, message: &[u8], signature: &[u8]) -> bool {
|
pub fn verify(&self, message: &[u8], signature: &[u8]) -> bool {
|
||||||
if signature.len() != DILITHIUM_SIGNATURE_SIZE {
|
if signature.len() != SIGNBYTES {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
pqc_dilithium::verify(signature, message, &self.inner.public).is_ok()
|
pqc_dilithium::verify(signature, message, &self.public_key).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the public key size in bytes.
|
/// Get the public key size in bytes.
|
||||||
|
|
@ -129,6 +239,13 @@ impl Default for DilithiumSigningKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for DilithiumSigningKey {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Zeroize secret key material on drop
|
||||||
|
self.secret_key.zeroize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Verify a Dilithium3 signature using only the public key.
|
/// Verify a Dilithium3 signature using only the public key.
|
||||||
///
|
///
|
||||||
/// This is useful when you only have the public key (e.g., verifying
|
/// This is useful when you only have the public key (e.g., verifying
|
||||||
|
|
@ -153,11 +270,23 @@ pub fn dilithium_verify(signature: &[u8], message: &[u8], public_key: &[u8]) ->
|
||||||
#[wasm_bindgen(js_name = dilithiumSizes)]
|
#[wasm_bindgen(js_name = dilithiumSizes)]
|
||||||
pub fn dilithium_sizes() -> js_sys::Object {
|
pub fn dilithium_sizes() -> js_sys::Object {
|
||||||
let obj = js_sys::Object::new();
|
let obj = js_sys::Object::new();
|
||||||
js_sys::Reflect::set(&obj, &"publicKey".into(), &(DILITHIUM_PUBLIC_KEY_SIZE as u32).into())
|
js_sys::Reflect::set(
|
||||||
|
&obj,
|
||||||
|
&"publicKey".into(),
|
||||||
|
&(DILITHIUM_PUBLIC_KEY_SIZE as u32).into(),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
js_sys::Reflect::set(&obj, &"secretKey".into(), &(DILITHIUM_SECRET_KEY_SIZE as u32).into())
|
js_sys::Reflect::set(
|
||||||
|
&obj,
|
||||||
|
&"secretKey".into(),
|
||||||
|
&(DILITHIUM_SECRET_KEY_SIZE as u32).into(),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
js_sys::Reflect::set(&obj, &"signature".into(), &(DILITHIUM_SIGNATURE_SIZE as u32).into())
|
js_sys::Reflect::set(
|
||||||
|
&obj,
|
||||||
|
&"signature".into(),
|
||||||
|
&(DILITHIUM_SIGNATURE_SIZE as u32).into(),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
obj
|
obj
|
||||||
}
|
}
|
||||||
|
|
@ -170,7 +299,7 @@ mod tests {
|
||||||
fn test_dilithium_keygen() {
|
fn test_dilithium_keygen() {
|
||||||
let key = DilithiumSigningKey::new();
|
let key = DilithiumSigningKey::new();
|
||||||
assert_eq!(key.public_key().len(), DILITHIUM_PUBLIC_KEY_SIZE);
|
assert_eq!(key.public_key().len(), DILITHIUM_PUBLIC_KEY_SIZE);
|
||||||
assert!(!key.secret_key().is_empty());
|
assert_eq!(key.secret_key().len(), DILITHIUM_SECRET_KEY_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -206,4 +335,56 @@ mod tests {
|
||||||
// Too long signature should fail
|
// Too long signature should fail
|
||||||
assert!(!key.verify(message, &[0u8; DILITHIUM_SIGNATURE_SIZE + 1]));
|
assert!(!key.verify(message, &[0u8; DILITHIUM_SIGNATURE_SIZE + 1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(dilithium_kat)]
|
||||||
|
fn test_dilithium_deterministic_keygen() {
|
||||||
|
let seed = [0xABu8; 64]; // Test seed
|
||||||
|
|
||||||
|
let key1 = DilithiumSigningKey::from_seed(&seed).unwrap();
|
||||||
|
let key2 = DilithiumSigningKey::from_seed(&seed).unwrap();
|
||||||
|
|
||||||
|
// Same seed should produce same public key
|
||||||
|
assert_eq!(key1.public_key(), key2.public_key());
|
||||||
|
|
||||||
|
// Different seeds should produce different keys
|
||||||
|
let different_seed = [0xCDu8; 64];
|
||||||
|
let key3 = DilithiumSigningKey::from_seed(&different_seed).unwrap();
|
||||||
|
assert_ne!(key1.public_key(), key3.public_key());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(dilithium_kat)]
|
||||||
|
fn test_dilithium_from_seed_sign_verify() {
|
||||||
|
let seed = [0x42u8; 64];
|
||||||
|
let key = DilithiumSigningKey::from_seed(&seed).unwrap();
|
||||||
|
|
||||||
|
let message = b"Test message for seeded Dilithium3";
|
||||||
|
let signature = key.sign(message);
|
||||||
|
|
||||||
|
assert_eq!(signature.len(), DILITHIUM_SIGNATURE_SIZE);
|
||||||
|
assert!(key.verify(message, &signature));
|
||||||
|
|
||||||
|
// Verify with standalone function
|
||||||
|
assert!(dilithium_verify(&signature, message, &key.public_key()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
fn test_seed_too_short() {
|
||||||
|
let short_seed = [0u8; 16];
|
||||||
|
let result = DilithiumSigningKey::from_seed(&short_seed);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Native version of seed length check test
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
fn test_seed_length_validation() {
|
||||||
|
// Just verify that short seeds are rejected
|
||||||
|
// The actual error is JsValue which only works on wasm32
|
||||||
|
let short_seed = [0u8; 16];
|
||||||
|
// This test verifies the compile-time check for DILITHIUM_SEED_SIZE
|
||||||
|
assert!(short_seed.len() < DILITHIUM_SEED_SIZE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
> Preparation and execution of mainnet launch
|
> Preparation and execution of mainnet launch
|
||||||
|
|
||||||
**Status**: 🔄 Pending
|
**Status**: ⏳ Blocked (Waiting for ecosystem completion)
|
||||||
**Priority**: Critical
|
**Priority**: Critical
|
||||||
**Components**: All
|
**Components**: All
|
||||||
|
|
||||||
|
|
@ -12,6 +12,12 @@
|
||||||
|
|
||||||
Finalize mainnet parameters, conduct genesis ceremony, establish initial token distribution, and deploy production infrastructure.
|
Finalize mainnet parameters, conduct genesis ceremony, establish initial token distribution, and deploy production infrastructure.
|
||||||
|
|
||||||
|
> **Important:** MAINNET launch is blocked until:
|
||||||
|
> 1. All ecosystem applications are developed and deployed (wallets, explorer, website)
|
||||||
|
> 2. TESTNET has been running with 99.9% uptime for 30+ days
|
||||||
|
> 3. All applications are tested and validated on TESTNET
|
||||||
|
> 4. External security audits are complete
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Tasks
|
## Tasks
|
||||||
|
|
|
||||||
|
|
@ -44,33 +44,43 @@ npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Task 3.2: Desktop Wallet
|
### Task 3.2: Desktop Wallet
|
||||||
- [ ] Tauri framework setup
|
- [x] Tauri framework setup
|
||||||
- [ ] Native file system access
|
- [x] Native file system access
|
||||||
|
- [x] BIP39 mnemonic generation
|
||||||
|
- [x] Argon2id + ChaCha20-Poly1305 encryption
|
||||||
|
- [x] Secure state management (auto-clear)
|
||||||
- [ ] System tray integration
|
- [ ] System tray integration
|
||||||
- [ ] Auto-updates
|
- [ ] Auto-updates
|
||||||
- [ ] OS keychain integration
|
- [ ] OS keychain integration
|
||||||
|
- [ ] Hardware wallet support
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- `apps/desktop/` (Planned)
|
- `apps/desktop-wallet/` (Implemented)
|
||||||
|
|
||||||
|
**Status:** 80% Complete
|
||||||
|
|
||||||
|
**Tech Stack:**
|
||||||
|
- Tauri 2.0 (Rust + React)
|
||||||
|
- React + TypeScript + Tailwind CSS
|
||||||
|
- Native Rust crypto (bip39, argon2, chacha20poly1305)
|
||||||
|
- Zustand for state management
|
||||||
|
|
||||||
|
### Task 3.3: Mobile Wallet
|
||||||
|
- [ ] Flutter setup (cross-platform)
|
||||||
|
- [ ] Biometric authentication (Face ID / Fingerprint)
|
||||||
|
- [ ] Push notifications
|
||||||
|
- [ ] Deep linking
|
||||||
|
- [ ] App store deployment (iOS + Android)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- `apps/mobile-wallet/` (Planned)
|
||||||
|
|
||||||
**Status:** Not Started
|
**Status:** Not Started
|
||||||
|
|
||||||
**Tech Stack:**
|
**Tech Stack:**
|
||||||
- Tauri (Rust + Web)
|
- Flutter (Dart) for cross-platform native performance
|
||||||
- Same React UI as web wallet
|
- flutter_secure_storage for encrypted keychain
|
||||||
- Native crypto via Rust bindings
|
- Dilithium3 via FFI bindings to Rust
|
||||||
|
|
||||||
### Task 3.3: Mobile Wallet
|
|
||||||
- [ ] React Native setup
|
|
||||||
- [ ] Biometric authentication
|
|
||||||
- [ ] Push notifications
|
|
||||||
- [ ] Deep linking
|
|
||||||
- [ ] App store deployment
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- `apps/mobile/` (Planned)
|
|
||||||
|
|
||||||
**Status:** Not Started
|
|
||||||
|
|
||||||
**Platforms:**
|
**Platforms:**
|
||||||
- iOS (App Store)
|
- iOS (App Store)
|
||||||
|
|
@ -189,22 +199,24 @@ mdbook build docs/
|
||||||
|
|
||||||
| Component | Progress | Status |
|
| Component | Progress | Status |
|
||||||
|-----------|----------|--------|
|
|-----------|----------|--------|
|
||||||
| Web Wallet | 70% | Foundation complete |
|
| Web Wallet | 70% | Foundation complete, needs Dilithium3 WASM |
|
||||||
| Desktop Wallet | 0% | Planned |
|
| Desktop Wallet | 80% | Tauri + security implemented |
|
||||||
| Mobile Wallet | 0% | Planned |
|
| Mobile Wallet | 0% | Planned (Flutter) |
|
||||||
| Explorer Frontend | 0% | Backend ready |
|
| Explorer Frontend | 90% | Home, Blocks, TX, Address, DAG, Network, Gas pages complete |
|
||||||
| Documentation | 60% | Guides complete |
|
| Documentation | 60% | Guides complete |
|
||||||
| API Providers | 0% | Planned |
|
| API Providers | 0% | Planned |
|
||||||
| Exchange Integration | 0% | Planned |
|
| Exchange Integration | 0% | Planned |
|
||||||
|
|
||||||
|
> **Note:** TESTNET deployment will be maintained until the entire ecosystem is developed, tested, and validated. MAINNET launch will only proceed after full ecosystem completion and satisfactory testnet performance.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
1. **Immediate:** Complete web wallet Dilithium3 WASM
|
1. **Immediate:** Complete web wallet Dilithium3 WASM
|
||||||
2. **Short-term:** Build explorer frontend
|
2. **Short-term:** Mobile wallet (Flutter)
|
||||||
3. **Medium-term:** Desktop wallet with Tauri
|
3. **Medium-term:** synor.cc website
|
||||||
4. **Long-term:** Mobile apps and exchange listings
|
4. **Long-term:** Exchange listings and API providers
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue