diff --git a/README.md b/README.md index cf79832..46ced15 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ hydentity/ ```bash # Clone the repository -git clone https://github.com/your-org/hydentity.git +git clone https://github.com/hydex-org/hydentity.git cd hydentity # Install dependencies diff --git a/packages/hydentity-sdk/src/client/hydentity-client.ts b/packages/hydentity-sdk/src/client/hydentity-client.ts index fa47f03..8652702 100644 --- a/packages/hydentity-sdk/src/client/hydentity-client.ts +++ b/packages/hydentity-sdk/src/client/hydentity-client.ts @@ -28,7 +28,11 @@ import { } from '../instruction-builders/vault'; import { buildUpdatePolicyInstruction, createUpdatePolicyParams } from '../instruction-builders/policy'; import { buildAddDelegateInstruction, buildRevokeDelegateInstruction } from '../instruction-builders/delegate'; -import { HYDENTITY_PROGRAM_ID } from '../constants'; +import { + HYDENTITY_PROGRAM_ID, + POLICY_MASTER_SEED_SIGN_MESSAGE, +} from '../constants'; +import { derivePolicyMasterSeedFromSignature } from '../utils/randomness'; /** * Configuration options for HydentityClient @@ -129,6 +133,21 @@ export class HydentityClient { return this.signer.getPublicKey(); } + /** + * Per-wallet entropy for PolicyEngine splits/delays (from signing POLICY_MASTER_SEED_SIGN_MESSAGE). + */ + private async derivePolicyMasterSeed(): Promise { + if (!this.signer) { + throw new Error('No signer configured. Call setSigner() first.'); + } + const message = new TextEncoder().encode(POLICY_MASTER_SEED_SIGN_MESSAGE); + const [signature, pubkey] = await Promise.all([ + this.signer.signMessage(message), + this.signer.getPublicKey(), + ]); + return derivePolicyMasterSeedFromSignature(pubkey.toBytes(), signature); + } + /** * Build, sign, and optionally send a transaction based on mode */ @@ -425,9 +444,8 @@ export class HydentityClient { throw new Error(`Insufficient vault balance: ${balance.sol} < ${amount}`); } - // Create policy engine for this claim - const signer = await this.getSignerPublicKey(); - const masterSeed = new Uint8Array(32); // TODO: Derive from signer + // Create policy engine for this claim (wallet-specific entropy via message sign) + const masterSeed = await this.derivePolicyMasterSeed(); const policyEngine = new PolicyEngine(masterSeed, policy.policyNonce); // Generate execution plan diff --git a/packages/hydentity-sdk/src/constants.ts b/packages/hydentity-sdk/src/constants.ts index 3a3ffa0..cf8507a 100644 --- a/packages/hydentity-sdk/src/constants.ts +++ b/packages/hydentity-sdk/src/constants.ts @@ -42,6 +42,16 @@ export const DUST_THRESHOLD_LAMPORTS = 10_000n; // 0.00001 SOL export const KMAC_DOMAIN_RANDOM_SEED = 'Hydentity - Random Seed'; export const KMAC_DOMAIN_SPLIT_SEED = 'Hydentity - Split Seed'; export const KMAC_DOMAIN_DELAY_SEED = 'Hydentity - Delay Seed'; +/** KMAC domain for hashing wallet proof into policy execution seed */ +export const KMAC_DOMAIN_POLICY_MASTER_SEED = 'Hydentity - Policy Master Seed'; + +/** + * Message bytes users sign to derive per-wallet policy execution entropy for + * split and delay plans. Does not authorize transfers by itself. + */ +export const POLICY_MASTER_SEED_SIGN_MESSAGE = + 'Sign to derive Hydentity private withdrawal split entropy. ' + + 'This does not grant any transfer.'; /** * SNS TLD key for .sol domains diff --git a/packages/hydentity-sdk/src/utils/randomness.test.ts b/packages/hydentity-sdk/src/utils/randomness.test.ts new file mode 100644 index 0000000..19569ed --- /dev/null +++ b/packages/hydentity-sdk/src/utils/randomness.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from 'vitest'; +import { derivePolicyMasterSeedFromSignature } from './randomness'; + +describe('derivePolicyMasterSeedFromSignature', () => { + it('rejects wrong pubkey length', () => { + expect(() => + derivePolicyMasterSeedFromSignature(new Uint8Array(31), new Uint8Array(64)) + ).toThrow('32 bytes'); + }); + + it('rejects wrong signature length', () => { + expect(() => + derivePolicyMasterSeedFromSignature(new Uint8Array(32), new Uint8Array(63)) + ).toThrow('64 bytes'); + }); + + it('is deterministic for the same proof', () => { + const pk = new Uint8Array(32).fill(7); + const sig = new Uint8Array(64).fill(3); + const a = derivePolicyMasterSeedFromSignature(pk, sig); + const b = derivePolicyMasterSeedFromSignature(pk, sig); + expect(a).toEqual(b); + expect(a.length).toBe(32); + }); + + it('differs when signature bytes change', () => { + const pk = new Uint8Array(32).fill(1); + const sig1 = new Uint8Array(64).fill(2); + const sig2 = new Uint8Array(64).fill(9); + const s1 = derivePolicyMasterSeedFromSignature(pk, sig1); + const s2 = derivePolicyMasterSeedFromSignature(pk, sig2); + expect(Array.from(s1).join()).not.toBe(Array.from(s2).join()); + }); +}); diff --git a/packages/hydentity-sdk/src/utils/randomness.ts b/packages/hydentity-sdk/src/utils/randomness.ts index 16abc3b..ad92fda 100644 --- a/packages/hydentity-sdk/src/utils/randomness.ts +++ b/packages/hydentity-sdk/src/utils/randomness.ts @@ -3,6 +3,7 @@ import { KMAC_DOMAIN_RANDOM_SEED, KMAC_DOMAIN_SPLIT_SEED, KMAC_DOMAIN_DELAY_SEED, + KMAC_DOMAIN_POLICY_MASTER_SEED, DUST_THRESHOLD_LAMPORTS, } from '../constants'; import type { Amount, U128 } from '../types/common'; @@ -49,6 +50,30 @@ export function deriveRandomSeed(masterSeed: Uint8Array, nonce: bigint): Uint8Ar ); } +/** + * Derive 32-byte policy master seed from a wallet message-signing proof. + * Callers must pass the Ed25519 signature of POLICY_MASTER_SEED_SIGN_MESSAGE. + */ +export function derivePolicyMasterSeedFromSignature( + walletPublicKey32: Uint8Array, + messageSignature64: Uint8Array +): Uint8Array { + if (walletPublicKey32.length !== 32) { + throw new Error('wallet public key must be 32 bytes'); + } + if (messageSignature64.length !== 64) { + throw new Error('Ed25519 signature must be 64 bytes'); + } + const input = new Uint8Array(32 + 64); + input.set(walletPublicKey32, 0); + input.set(messageSignature64, 32); + return kmac256( + new TextEncoder().encode(KMAC_DOMAIN_POLICY_MASTER_SEED), + input, + { dkLen: 32 } + ); +} + /** * Generate a deterministic random value from seed and index *