From 64089d1fcaea0d9f96f76d043d36ba9678f7fa76 Mon Sep 17 00:00:00 2001 From: Christophe Diederichs Date: Tue, 23 May 2023 18:21:23 +0100 Subject: [PATCH] add crypto_tweak api --- crypto_sign_ed25519.js | 5 +- crypto_tweak.js | 216 +++++++++++++++++++++++++++++++++++ test/crypto_tweak_ed25519.js | 113 ++++++++++++++++++ 3 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 crypto_tweak.js create mode 100644 test/crypto_tweak_ed25519.js diff --git a/crypto_sign_ed25519.js b/crypto_sign_ed25519.js index b0c2760..bf2bf64 100644 --- a/crypto_sign_ed25519.js +++ b/crypto_sign_ed25519.js @@ -370,5 +370,8 @@ module.exports = { crypto_sign_BYTES, crypto_sign_PUBLICKEYBYTES, crypto_sign_SECRETKEYBYTES, - crypto_sign_SEEDBYTES + crypto_sign_SEEDBYTES, + crypto_sign_ed25519_PUBLICKEYBYTES, + crypto_sign_ed25519_SECRETKEYBYTES, + crypto_sign_ed25519_SEEDBYTES } diff --git a/crypto_tweak.js b/crypto_tweak.js new file mode 100644 index 0000000..40c3664 --- /dev/null +++ b/crypto_tweak.js @@ -0,0 +1,216 @@ +const b4a = require('b4a') + +const { + crypto_scalarmult_ed25519, + crypto_scalarmult_ed25519_noclamp, + crypto_scalarmult_ed25519_base, + crypto_scalarmult_ed25519_base_noclamp, + crypto_scalarmult_ed25519_SCALARBYTES +} = require('./crypto_scalarmult_ed25519') + +const { + crypto_sign_keypair_seed, + crypto_sign_ed25519_PUBLICKEYBYTES +} = require('./crypto_sign_ed25519') + +const { + crypto_hash_sha512_state, + crypto_hash_sha512_update, + crypto_hash_sha512_final, + crypto_hash +} = require('./crypto_hash') + +const { + crypto_core_ed25519_is_valid_point, + crypto_core_ed25519_scalar_reduce, + crypto_core_ed25519_scalar_add, + crypto_core_ed25519_scalar_mul, + crypto_core_ed25519_add +} = require('./crypto_core') + +const { curve25519_h: COFACTOR } = require('./fe25519_25') + +const { sodium_memzero } = require('./utils') + +/* + *EXPERIMENTAL API* + + This module is an experimental implementation of a key tweaking protocol + over ed25519 keys. The signature algorithm has been reimplemented from + libsodium, but the nonce generation algorithm is *non-standard*. + + Use at your own risk +*/ + +const crypto_tweak_ed25519_BYTES = crypto_sign_ed25519_PUBLICKEYBYTES +const crypto_tweak_ed25519_SCALARBYTES = crypto_scalarmult_ed25519_SCALARBYTES + +function _crypto_tweak_nonce (nonce, n, m, mlen) { + // dom2(x, y) with x = 0 (not prehashed) and y = "crypto_tweak_ed25519" + const prefix = b4a.alloc(54) + + prefix.write('SigEd25519 no Ed25519 collisions\x00\x14crypto_tweak_ed25519') + + const hs = crypto_hash_sha512_state() + + crypto_hash_sha512_update(hs, prefix) + crypto_hash_sha512_update(hs, n, 32) + crypto_hash_sha512_update(hs, m, m.byteLength) + crypto_hash_sha512_final(hs, nonce) +} + +function _crypto_sign_ed25519_clamp (k) { + k[0] &= 248 + k[31] &= 127 + k[31] |= 64 +} + +function _crypto_tweak_ed25519(q, n, ns) { + sodium_memzero(q) + + crypto_hash(n, ns) + n[31] &= 127 // clear highest bit + + crypto_scalarmult_ed25519_base_noclamp(q, n) + + // hash tweak until we get a valid tweaked q + while (crypto_core_ed25519_is_valid_point(q) != 1) { + crypto_hash(n, n, 32) + n[31] &= 127 // clear highest bit + + crypto_scalarmult_ed25519_base_noclamp(q, n) + } +} + +function crypto_tweak_ed25519_base (n, q, ns) { + sodium_memzero(q) + + const n64 = b4a.alloc(64) + _crypto_tweak_ed25519(q, n64, ns) + + n.set(n64.subarray(0, 32)) +} + +// TODO: check pk is correct if we pass it +function crypto_tweak_ed25519_sign_detached (sig, m, n, pk = null) { + const hs = crypto_hash_sha512_state() + + const nonce = b4a.alloc(64) + const R = b4a.alloc(32) + const hram = b4a.alloc(64) + const _pk = b4a.alloc(32) + + // check if pk was passed + if (pk === null) { + pk = _pk + + // derive pk from scalar + crypto_scalarmult_ed25519_base_noclamp(pk, n) + } + + _crypto_tweak_nonce(nonce, n, m) + crypto_core_ed25519_scalar_reduce(nonce, nonce) + + // R = G ^ nonce : curve point from nonce + crypto_scalarmult_ed25519_base_noclamp(R, nonce) + + // generate challenge as h(ram) = hash(R, pk, message) + crypto_hash_sha512_update(hs, R, 32) + crypto_hash_sha512_update(hs, pk, 32) + crypto_hash_sha512_update(hs, m, m.byteLength) + + crypto_hash_sha512_final(hs, hram) + + crypto_core_ed25519_scalar_reduce(hram, hram) + + // sig = nonce + n * h(ram) + crypto_core_ed25519_scalar_mul(sig.subarray(0, 32), hram.subarray(0, 32), n) + crypto_core_ed25519_scalar_add(sig.subarray(32, 64), nonce.subarray(0, 32), sig) + + sig.set(R) + + return 0 +} + +// get scalar from secret key +function crypto_tweak_ed25519_sk_to_scalar (n, sk) { + const n64 = b4a.alloc(64) + + // get sk scalar from seed, cf. crypto_sign_keypair_seed + crypto_hash(n64, sk, 32) + _crypto_sign_ed25519_clamp(n64) + + n.set(n64.subarray(0, 32)) +} + +// tweak a secret key +function crypto_tweak_ed25519_scalar (scalar_out, scalar, ns) { + const q = b4a.alloc(32) + const n = b4a.alloc(64) + + const n32 = n.subarray(0, 32) + + _crypto_tweak_ed25519(q, n, ns) + crypto_tweak_ed25519_scalar_add(scalar_out, n32, scalar) +} + +// tweak a public key +function crypto_tweak_ed25519_pk (tpk, pk, ns) { + const n = b4a.alloc(64) + const q = b4a.alloc(32) + + _crypto_tweak_ed25519(q, n, ns) + return crypto_core_ed25519_add(tpk, q, pk) +} + +function crypto_tweak_ed25519_keypair (pk, scalar_out, scalar, ns) { + const n64 = b4a.alloc(64) + + crypto_hash(n64, ns) + n64[31] &= 127 // clear highest bit + + crypto_tweak_ed25519_scalar_add(scalar_out, scalar, n64) + crypto_scalarmult_ed25519_base_noclamp(pk, scalar_out) + + // hash tweak until we get a valid tweaked point + while (crypto_core_ed25519_is_valid_point(pk) != 1) { + crypto_hash(n64, n64, 32) + n64[31] &= 127 // clear highest bit + + crypto_tweak_ed25519_scalar_add(scalar_out, scalar, n64) + crypto_scalarmult_ed25519_base_noclamp(pk, scalar_out) + } +} + +// add tweak to scalar +function crypto_tweak_ed25519_scalar_add (scalar_out, scalar, n) { + crypto_core_ed25519_scalar_add(scalar_out, scalar, n) +} + +// add tweak point to public key +function crypto_tweak_ed25519_pk_add (tpk, pk, q) { + crypto_core_ed25519_add(tpk, pk, q) +} + +// add tweak to scalar +function crypto_tweak_ed25519_scalar_mul (scalar_out, scalar, n) { + crypto_core_ed25519_scalar_mul(scalar_out, scalar, n) +} + +// add tweak point to public key +function crypto_tweak_ed25519_publickey_mul (tpk, pk, n) { + crypto_scalarmult_ed25519_noclamp(tpk, n, pk) +} + +module.exports = { + crypto_tweak_ed25519_base, + crypto_tweak_ed25519_sign_detached, + crypto_tweak_ed25519_sk_to_scalar, + crypto_tweak_ed25519_scalar, + crypto_tweak_ed25519_pk, + crypto_tweak_ed25519_keypair, + crypto_tweak_ed25519_scalar_add, + crypto_tweak_ed25519_pk_add, + crypto_tweak_ed25519_BYTES, + crypto_tweak_ed25519_SCALARBYTES +} diff --git a/test/crypto_tweak_ed25519.js b/test/crypto_tweak_ed25519.js new file mode 100644 index 0000000..1efb925 --- /dev/null +++ b/test/crypto_tweak_ed25519.js @@ -0,0 +1,113 @@ +const test = require('brittle') +const sodium = require('..') +const fixtures = require('./fixtures/crypto_tweak_ed25519_sign.js') + +test('crypto_tweak', function (t) { + const pk = Buffer.alloc(sodium.crypto_sign_PUBLICKEYBYTES) + const sk = Buffer.alloc(sodium.crypto_sign_SECRETKEYBYTES) + + sodium.crypto_sign_keypair(pk, sk) + + const tpk = Buffer.alloc(sodium.crypto_tweak_ed25519_BYTES) + const tsk = Buffer.alloc(sodium.crypto_tweak_ed25519_SCALARBYTES) + + const tkpk = Buffer.alloc(sodium.crypto_tweak_ed25519_BYTES) + const tksk = Buffer.alloc(sodium.crypto_tweak_ed25519_SCALARBYTES) + + const point = Buffer.alloc(sodium.crypto_tweak_ed25519_BYTES) + const tweak = Buffer.alloc(sodium.crypto_tweak_ed25519_SCALARBYTES) + + const ns = Buffer.alloc(32) + sodium.crypto_generichash(ns, Buffer.from('namespace')) + + t.exception.all(function () { + sodium.crypto_tweak_ed25519_base() + }, 'should validate input') + + t.exception.all(function () { + sodium.crypto_tweak_ed25519_base(Buffer.alloc(0), Buffer.alloc(0), ns) + }, 'should validate input length') + + sodium.crypto_tweak_ed25519_base(tweak, point, ns) + + const _sk = sk.subarray(0, 32) + sodium.crypto_tweak_ed25519_sk_to_scalar(_sk, sk) + + sodium.crypto_tweak_ed25519_pk(tpk, pk, ns) + sodium.crypto_tweak_ed25519_scalar(tsk, _sk, ns) + + sodium.crypto_tweak_ed25519_keypair(tkpk, tksk, _sk, ns) + + sodium.crypto_tweak_ed25519_pk_add(pk, pk, point) + sodium.crypto_tweak_ed25519_scalar_add(_sk, _sk, tweak) + + t.alike(pk, tpk, 'tweak public key') + t.alike(_sk, tsk, 'tweak secret key') + t.alike(pk, tkpk, 'tweak keypair public key') + t.alike(_sk, tksk, 'tweak keypair secret key') +}) + +test('crypto_tweak_sign', function (t) { + const pk = Buffer.alloc(sodium.crypto_sign_PUBLICKEYBYTES) + const sk = Buffer.alloc(sodium.crypto_sign_SECRETKEYBYTES) + + sodium.crypto_sign_keypair(pk, sk) + + const tpk = Buffer.alloc(sodium.crypto_tweak_ed25519_BYTES) + const tsk = Buffer.alloc(sodium.crypto_tweak_ed25519_SCALARBYTES) + + const point = Buffer.alloc(sodium.crypto_tweak_ed25519_BYTES) + const tweak = Buffer.alloc(sodium.crypto_tweak_ed25519_SCALARBYTES) + + const ns = Buffer.alloc(32) + sodium.crypto_generichash(ns, Buffer.from('namespace')) + + sodium.crypto_tweak_ed25519_base(tweak, point, ns) + + sodium.crypto_tweak_ed25519_pk(tpk, pk, ns) + sodium.crypto_tweak_ed25519_sk_to_scalar(tsk, sk) + sodium.crypto_tweak_ed25519_scalar(tsk, tsk, ns) + + sodium.crypto_tweak_ed25519_pk_add(pk, pk, point) + + const _sk = sk.subarray(0, 32) + sodium.crypto_tweak_ed25519_sk_to_scalar(_sk, sk) + sodium.crypto_tweak_ed25519_scalar_add(_sk, _sk, tweak) + + const m = Buffer.from('test message') + const sig = Buffer.alloc(sodium.crypto_sign_BYTES) + + sodium.crypto_tweak_ed25519_sign_detached(sig, m, _sk) + t.ok(sodium.crypto_sign_verify_detached(sig, m, pk)) + t.ok(sodium.crypto_sign_verify_detached(sig, m, tpk)) + + sodium.crypto_tweak_ed25519_sign_detached(sig, m, tsk) + t.ok(sodium.crypto_sign_verify_detached(sig, m, pk)) + t.ok(sodium.crypto_sign_verify_detached(sig, m, tpk)) +}) + +test('crypto_tweak sign fixtures', t => { + for (const f of fixtures) { + const [sk, n, m, sig, tweak, tpk, tn] = f.map(Buffer.from) + + const signature = Buffer.alloc(64) + const scalar = Buffer.alloc(32) + const pk = Buffer.alloc(32) + + sodium.crypto_tweak_ed25519_sk_to_scalar(scalar, sk) + t.alike(scalar, n) + + sodium.crypto_tweak_ed25519_sign_detached(signature, m, n) + t.alike(signature, sig) + + sodium.randombytes_buf(signature) + sodium.crypto_sign_ed25519_sk_to_pk(pk, sk) + + sodium.crypto_tweak_ed25519_sign_detached(signature, m, n, pk) + t.alike(signature, sig) + + sodium.crypto_tweak_ed25519_keypair(pk, scalar, n, tweak) + t.alike(pk, tpk) + t.alike(scalar, tn) + } +})