Add composite crypto functions to provider and rewire remaining imports

Add 17 new provider-level functions (derivationToScalar, deriveViewTag,
  ecdhDecodeFull, computeCarrotSpendPubkey, randomScalar, etc.) that
  compose backend primitives. Rewire all consumer files to import through
  the crypto provider. Only internal JS point serialization (pointFromBytes/
  pointToBytes) and source implementation files retain direct imports.
This commit is contained in:
Matt Hess
2026-02-01 03:39:36 +00:00
parent 64d9d40470
commit 9c6b2943b9
13 changed files with 200 additions and 34 deletions
+1 -3
View File
@@ -17,7 +17,7 @@
import { hexToBytes, bytesToHex } from './address.js';
import { pointFromBytes, pointToBytes } from './ed25519.js';
import { blake2b, keccak256, scalarMultPoint } from './crypto/index.js';
import { blake2b, keccak256, scalarMultBase, scalarMultPoint, pointAddCompressed, hashToPoint } from './crypto/index.js';
// Group order L for scalar reduction
const L = (1n << 252n) + 27742317777372353535851937790883648493n;
@@ -697,8 +697,6 @@ export function scanCarrotOutput(output, viewIncomingKey, accountSpendPubkey, in
// Import additional ed25519 functions
// ============================================================================
import { scalarMultBase, pointAddCompressed, hashToPoint } from './crypto/index.js';
// Group order L for scalar reduction (also defined at top for clarity)
const L_ORDER = (1n << 252n) + 27742317777372353535851937790883648493n;
+4 -2
View File
@@ -4,8 +4,10 @@
*/
import { hexToBytes, bytesToHex } from './address.js';
import { computeCarrotSpendPubkey, computeCarrotMainAddressViewPubkey, computeCarrotAccountViewPubkey } from './ed25519.js';
import { blake2b, keccak256, scalarMultBase } from './crypto/index.js';
import {
blake2b, keccak256, scalarMultBase,
computeCarrotSpendPubkey, computeCarrotMainAddressViewPubkey, computeCarrotAccountViewPubkey,
} from './crypto/index.js';
// Group order L for scalar reduction
const L = (1n << 252n) + 27742317777372353535851937790883648493n;
+19 -1
View File
@@ -12,15 +12,33 @@ export {
setCryptoBackend,
getCryptoBackend,
getCurrentBackendType,
keccak256,
// Hashing
keccak256, keccak256Hex, cnFastHash,
blake2b,
// Scalar ops
scAdd, scSub, scMul, scMulAdd, scMulSub,
scReduce32, scReduce64, scInvert, scCheck, scIsZero,
scalarAdd,
// Point ops
scalarMultBase, scalarMultPoint, pointAddCompressed,
pointSubCompressed, pointNegate, doubleScalarMultBase,
isIdentity,
// Constants
getGeneratorG, getGeneratorT,
// Random
randomScalar,
// Hash-to-point & key derivation
hashToPoint, generateKeyImage, generateKeyDerivation,
derivePublicKey, deriveSecretKey,
derivationToScalar, deriveViewTag, deriveSubaddressPublicKey,
computeSharedSecret,
// Amount encryption/decryption
ecdhDecode, ecdhDecodeFull, ecdhEncode,
// Pedersen commitments
commit, zeroCommit, genCommitmentMask,
// CARROT key derivation
computeCarrotSpendPubkey, computeCarrotAccountViewPubkey,
computeCarrotMainAddressViewPubkey,
} from './provider.js';
// Backends (for direct access / testing)
+157
View File
@@ -14,6 +14,7 @@
*/
import { JsCryptoBackend } from './backend-js.js';
import { hexToBytes } from '../address.js';
let currentBackend = null;
let backendType = 'js';
@@ -100,3 +101,159 @@ export function deriveSecretKey(derivation, outputIndex, baseSec) { return getCr
export function commit(amount, mask) { return getCryptoBackend().commit(amount, mask); }
export function zeroCommit(amount) { return getCryptoBackend().zeroCommit(amount); }
export function genCommitmentMask(sharedSecret) { return getCryptoBackend().genCommitmentMask(sharedSecret); }
// =============================================================================
// Composite functions — built on top of backend primitives
// =============================================================================
// Aliases
export const cnFastHash = keccak256;
export function keccak256Hex(input) {
const hash = keccak256(input);
return Array.from(hash).map(b => b.toString(16).padStart(2, '0')).join('');
}
// Constants
const G_BYTES = new Uint8Array([
0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
]);
const T_BYTES = new Uint8Array([
0x96, 0x6f, 0xc6, 0x6b, 0x82, 0xcd, 0x56, 0xcf,
0x85, 0xea, 0xec, 0x80, 0x1c, 0x42, 0x84, 0x5f,
0x5f, 0x40, 0x88, 0x78, 0xd1, 0x56, 0x1e, 0x00,
0xd3, 0xd7, 0xde, 0xd2, 0x79, 0x4d, 0x09, 0x4f,
]);
export function getGeneratorG() { return new Uint8Array(G_BYTES); }
export function getGeneratorT() { return new Uint8Array(T_BYTES); }
export function isIdentity(p) {
if (p[0] !== 1) return false;
for (let i = 1; i < 32; i++) {
if (p[i] !== 0) return false;
}
return true;
}
// Random scalar: generate 64 random bytes, reduce mod L
export function randomScalar() {
let bytes64;
if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.getRandomValues) {
bytes64 = new Uint8Array(64);
globalThis.crypto.getRandomValues(bytes64);
} else {
const { randomBytes } = require('crypto');
bytes64 = new Uint8Array(randomBytes(64));
}
return scReduce64(bytes64);
}
// Varint encoding (internal helper)
function encodeVarint(n) {
const bytes = [];
while (n >= 0x80) {
bytes.push((n & 0x7f) | 0x80);
n >>>= 7;
}
bytes.push(n);
return new Uint8Array(bytes);
}
// Key derivation composites
export function derivationToScalar(derivation, outputIndex) {
if (typeof derivation === 'string') {
derivation = hexToBytes(derivation);
}
const indexBytes = encodeVarint(outputIndex);
const input = new Uint8Array(derivation.length + indexBytes.length);
input.set(derivation);
input.set(indexBytes, derivation.length);
return scReduce32(keccak256(input));
}
export function computeSharedSecret(derivation, outputIndex) {
return derivationToScalar(derivation, outputIndex);
}
export function deriveViewTag(derivation, outputIndex) {
if (typeof derivation === 'string') {
derivation = hexToBytes(derivation);
}
const salt = new TextEncoder().encode('view_tag');
const indexBytes = encodeVarint(outputIndex);
const input = new Uint8Array(salt.length + derivation.length + indexBytes.length);
input.set(salt);
input.set(derivation, salt.length);
input.set(indexBytes, salt.length + derivation.length);
return keccak256(input)[0];
}
export function deriveSubaddressPublicKey(outputKey, derivation, outputIndex) {
if (typeof outputKey === 'string') outputKey = hexToBytes(outputKey);
if (typeof derivation === 'string') derivation = hexToBytes(derivation);
const scalar = derivationToScalar(derivation, outputIndex);
const scalarG = scalarMultBase(scalar);
const scalarGNeg = new Uint8Array(scalarG);
scalarGNeg[31] ^= 0x80;
return pointAddCompressed(outputKey, scalarGNeg);
}
// Amount decryption
function genAmountEncodingFactor(sharedSecret) {
const prefix = new TextEncoder().encode('amount');
const input = new Uint8Array(prefix.length + sharedSecret.length);
input.set(prefix);
input.set(sharedSecret, prefix.length);
return keccak256(input);
}
export function ecdhDecode(encryptedAmount, sharedSecret) {
if (typeof sharedSecret === 'string') sharedSecret = hexToBytes(sharedSecret);
const encodingFactor = genAmountEncodingFactor(sharedSecret);
const decrypted = new Uint8Array(8);
for (let i = 0; i < 8; i++) decrypted[i] = encryptedAmount[i] ^ encodingFactor[i];
let amount = 0n;
for (let i = 7; i >= 0; i--) amount = (amount << 8n) | BigInt(decrypted[i]);
return amount;
}
export function ecdhDecodeFull(encryptedAmount, sharedSecret) {
if (typeof sharedSecret === 'string') sharedSecret = hexToBytes(sharedSecret);
const amount = ecdhDecode(encryptedAmount, sharedSecret);
const mask = genCommitmentMask(sharedSecret);
return { amount, mask };
}
export function ecdhEncode(amount, sharedSecret) {
if (typeof sharedSecret === 'string') sharedSecret = hexToBytes(sharedSecret);
const encodingFactor = genAmountEncodingFactor(sharedSecret);
const amountBytes = new Uint8Array(8);
let n = BigInt(amount);
for (let i = 0; i < 8; i++) { amountBytes[i] = Number(n & 0xffn); n >>= 8n; }
const encrypted = new Uint8Array(8);
for (let i = 0; i < 8; i++) encrypted[i] = amountBytes[i] ^ encodingFactor[i];
return encrypted;
}
// CARROT key composites
export function computeCarrotSpendPubkey(k_gi, k_ps) {
const giG = scalarMultBase(k_gi);
const psT = scalarMultPoint(k_ps, T_BYTES);
return pointAddCompressed(giG, psT);
}
export function computeCarrotAccountViewPubkey(k_vi, K_s) {
return scalarMultPoint(k_vi, K_s);
}
export function computeCarrotMainAddressViewPubkey(k_vi) {
return scalarMultBase(k_vi);
}
// Scalar add (simple wrapper around scAdd)
export function scalarAdd(a, b) { return scAdd(a, b); }
+1 -2
View File
@@ -8,8 +8,7 @@
* Reference: cryptonote_basic/miner.cpp, cryptonote_basic/difficulty.cpp
*/
import { cnFastHash } from './keccak.js';
import { keccak256 } from './crypto/index.js';
import { keccak256, cnFastHash } from './crypto/index.js';
import { encodeVarint, serializeBlockHeader, HF_VERSION_ENABLE_ORACLE } from './transaction.js';
// =============================================================================
+4 -3
View File
@@ -13,9 +13,10 @@
*/
import { bytesToHex, hexToBytes } from './address.js';
import { computeCarrotSpendPubkey, computeCarrotMainAddressViewPubkey, computeCarrotAccountViewPubkey } from './ed25519.js';
import { keccak256, scalarMultBase, scalarMultPoint, pointAddCompressed } from './crypto/index.js';
import { scAdd } from './transaction.js';
import {
keccak256, scalarMultBase, scalarMultPoint, pointAddCompressed, scAdd,
computeCarrotSpendPubkey, computeCarrotMainAddressViewPubkey, computeCarrotAccountViewPubkey,
} from './crypto/index.js';
import { MultisigAccount } from './multisig.js';
import {
makeViewBalanceSecret,
+1 -3
View File
@@ -18,9 +18,7 @@
*/
import { bytesToHex, hexToBytes } from './address.js';
import { randomScalar } from './ed25519.js';
import { keccak256, scalarMultBase, scalarMultPoint, pointAddCompressed } from './crypto/index.js';
import { scReduce32, scAdd, scMul, scMulAdd } from './transaction.js';
import { keccak256, scalarMultBase, scalarMultPoint, pointAddCompressed, randomScalar, scReduce32, scAdd, scMul, scMulAdd } from './crypto/index.js';
import { encode as base58Encode, decode as base58Decode } from './base58.js';
// ============================================================================
+4 -4
View File
@@ -11,10 +11,10 @@
* @module testnet/miner-tx
*/
import { randomScalar } from '../ed25519.js';
import { deriveViewTag } from '../scanning.js';
import { scalarMultBase, generateKeyDerivation, derivePublicKey } from '../crypto/index.js';
import { cnFastHash } from '../keccak.js';
import {
scalarMultBase, generateKeyDerivation, derivePublicKey,
randomScalar, deriveViewTag, cnFastHash,
} from '../crypto/index.js';
import { serializeTxPrefix, encodeVarint } from '../transaction/serialization.js';
import { bytesToHex, hexToBytes } from '../address.js';
import { TX_TYPE, RCT_TYPE } from '../transaction/constants.js';
+1 -1
View File
@@ -10,7 +10,7 @@
import { RandomXContext } from '../randomx/index.js';
import { constructBlockHashingBlob, findNonceOffset, setNonce, checkHash } from '../mining.js';
import { getBlockHash, serializeBlockHeader } from '../block/serialization.js';
import { cnFastHash } from '../keccak.js';
import { cnFastHash } from '../crypto/index.js';
import { serializeTxPrefix } from '../transaction/serialization.js';
import { bytesToHex, hexToBytes } from '../address.js';
import { createMinerTransaction, createEmptyProtocolTransaction } from './miner-tx.js';
+3 -4
View File
@@ -10,12 +10,11 @@
* Reference: Salvium/Monero src/ringct/rctOps.cpp, src/ringct/rctSigs.cpp
*/
import { keccak256Hex } from './keccak.js';
import { getGeneratorG, getGeneratorT } from './ed25519.js';
import { derivationToScalar } from './scanning.js';
import {
keccak256, scalarMultBase, scalarMultPoint, pointAddCompressed,
keccak256, keccak256Hex, scalarMultBase, scalarMultPoint, pointAddCompressed,
getGeneratorG, getGeneratorT,
generateKeyDerivation, derivePublicKey, deriveSecretKey,
derivationToScalar,
hashToPoint, generateKeyImage,
} from './crypto/index.js';
import { bytesToHex, hexToBytes } from './address.js';
+1 -2
View File
@@ -15,11 +15,10 @@
*/
import { hexToBytes, bytesToHex } from '../address.js';
import { getGeneratorT } from '../ed25519.js';
import { edwardsToMontgomeryU, x25519ScalarMult } from '../carrot-scanning.js';
import {
blake2b, scalarMultBase, scalarMultPoint, pointAddCompressed,
scReduce32, commit,
scReduce32, commit, getGeneratorT,
} from '../crypto/index.js';
import { CARROT_DOMAIN, CARROT_ENOTE_TYPE } from './constants.js';
+2 -7
View File
@@ -11,13 +11,6 @@
*/
import { WalletOutput, WalletTransaction } from './wallet-store.js';
import {
deriveViewTag,
computeSharedSecret,
ecdhDecodeFull,
deriveSubaddressPublicKey,
scalarAdd
} from './scanning.js';
import { cnSubaddressSecretKey, carrotIndexExtensionGenerator, carrotSubaddressScalar } from './subaddress.js';
import { scanCarrotOutput, makeInputContext, makeInputContextCoinbase, generateCarrotKeyImage } from './carrot-scanning.js';
import { parseTransaction, extractTxPubKey, extractPaymentId } from './transaction.js';
@@ -26,6 +19,8 @@ import { TX_TYPE } from './wallet.js';
import {
generateKeyDerivation, derivePublicKey, deriveSecretKey,
generateKeyImage, commit as pedersonCommit,
deriveViewTag, computeSharedSecret, ecdhDecodeFull,
deriveSubaddressPublicKey, scalarAdd,
} from './crypto/index.js';
// ============================================================================
+2 -2
View File
@@ -19,8 +19,8 @@
import { generateSeed, deriveKeys, deriveCarrotKeys } from './carrot.js';
import { createAddress, parseAddress, hexToBytes, bytesToHex } from './address.js';
import { cnSubaddress } from './subaddress.js';
import { derivationToScalar, scanTransaction } from './scanning.js';
import { generateKeyDerivation, deriveSecretKey, generateKeyImage, scalarMultBase } from './crypto/index.js';
import { scanTransaction } from './scanning.js';
import { generateKeyDerivation, deriveSecretKey, generateKeyImage, scalarMultBase, derivationToScalar } from './crypto/index.js';
import {
buildTransaction,
buildStakeTransaction,