diff options
Diffstat (limited to 'js/axon.js')
| -rw-r--r-- | js/axon.js | 30 |
1 files changed, 28 insertions, 2 deletions
| @@ -8,6 +8,7 @@ | |||
| 8 | 8 | ||
| 9 | import { ed25519, x25519, edwardsToMontgomeryPriv, edwardsToMontgomeryPub } from '@noble/curves/ed25519'; | 9 | import { ed25519, x25519, edwardsToMontgomeryPriv, edwardsToMontgomeryPub } from '@noble/curves/ed25519'; |
| 10 | import { sha256 } from '@noble/hashes/sha256'; | 10 | import { sha256 } from '@noble/hashes/sha256'; |
| 11 | import { hkdf } from '@noble/hashes/hkdf'; | ||
| 11 | import { chacha20poly1305 } from '@noble/ciphers/chacha'; | 12 | import { chacha20poly1305 } from '@noble/ciphers/chacha'; |
| 12 | import { encode, decode } from '@msgpack/msgpack'; | 13 | import { encode, decode } from '@msgpack/msgpack'; |
| 13 | 14 | ||
| @@ -287,6 +288,25 @@ function dhSharedSecret(localSeed, remoteEd25519Pub) { | |||
| 287 | } | 288 | } |
| 288 | 289 | ||
| 289 | /** | 290 | /** |
| 291 | * dmKey derives a 32-byte symmetric key from the raw DH shared secret | ||
| 292 | * via HKDF-SHA256, matching Go's dmKey(). | ||
| 293 | */ | ||
| 294 | function dmKey(shared) { | ||
| 295 | return hkdf(sha256, shared, undefined, 'axon-dm-v1', 32); | ||
| 296 | } | ||
| 297 | |||
| 298 | /** | ||
| 299 | * dmAD builds associated data by concatenating sender + recipient Ed25519 | ||
| 300 | * public keys, matching Go's dmAD(). | ||
| 301 | */ | ||
| 302 | function dmAD(senderPub, recipientPub) { | ||
| 303 | const ad = new Uint8Array(senderPub.length + recipientPub.length); | ||
| 304 | ad.set(senderPub); | ||
| 305 | ad.set(recipientPub, senderPub.length); | ||
| 306 | return ad; | ||
| 307 | } | ||
| 308 | |||
| 309 | /** | ||
| 290 | * encryptDM(senderSeed, recipientPubkey, plaintext) → Uint8Array | 310 | * encryptDM(senderSeed, recipientPubkey, plaintext) → Uint8Array |
| 291 | * | 311 | * |
| 292 | * Returns nonce(12) || ciphertext+tag, matching Go's EncryptDM. | 312 | * Returns nonce(12) || ciphertext+tag, matching Go's EncryptDM. |
| @@ -296,8 +316,11 @@ export function encryptDM(senderSeed, recipientPubkey, plaintext) { | |||
| 296 | const enc = new TextEncoder(); | 316 | const enc = new TextEncoder(); |
| 297 | const pt = typeof plaintext === 'string' ? enc.encode(plaintext) : plaintext; | 317 | const pt = typeof plaintext === 'string' ? enc.encode(plaintext) : plaintext; |
| 298 | const shared = dhSharedSecret(senderSeed, recipientPubkey); | 318 | const shared = dhSharedSecret(senderSeed, recipientPubkey); |
| 319 | const key = dmKey(shared); | ||
| 299 | const nonce = crypto.getRandomValues(new Uint8Array(12)); | 320 | const nonce = crypto.getRandomValues(new Uint8Array(12)); |
| 300 | const ct = chacha20poly1305(shared, nonce).encrypt(pt); | 321 | const senderPub = ed25519.getPublicKey(senderSeed); |
| 322 | const ad = dmAD(senderPub, recipientPubkey); | ||
| 323 | const ct = chacha20poly1305(key, nonce, ad).encrypt(pt); | ||
| 301 | const out = new Uint8Array(12 + ct.length); | 324 | const out = new Uint8Array(12 + ct.length); |
| 302 | out.set(nonce); | 325 | out.set(nonce); |
| 303 | out.set(ct, 12); | 326 | out.set(ct, 12); |
| @@ -312,9 +335,12 @@ export function encryptDM(senderSeed, recipientPubkey, plaintext) { | |||
| 312 | export function decryptDM(recipientSeed, senderPubkey, blob) { | 335 | export function decryptDM(recipientSeed, senderPubkey, blob) { |
| 313 | if (blob.length < 12) throw new Error('axon: dm blob too short'); | 336 | if (blob.length < 12) throw new Error('axon: dm blob too short'); |
| 314 | const shared = dhSharedSecret(recipientSeed, senderPubkey); | 337 | const shared = dhSharedSecret(recipientSeed, senderPubkey); |
| 338 | const key = dmKey(shared); | ||
| 315 | const nonce = blob.slice(0, 12); | 339 | const nonce = blob.slice(0, 12); |
| 316 | const ct = blob.slice(12); | 340 | const ct = blob.slice(12); |
| 317 | return chacha20poly1305(shared, nonce).decrypt(ct); | 341 | const recipientPub = ed25519.getPublicKey(recipientSeed); |
| 342 | const ad = dmAD(senderPubkey, recipientPub); | ||
| 343 | return chacha20poly1305(key, nonce, ad).decrypt(ct); | ||
| 318 | } | 344 | } |
| 319 | 345 | ||
| 320 | // --------------------------------------------------------------------------- | 346 | // --------------------------------------------------------------------------- |
