Add Pedersen commitment functions to WASM crypto backend (Phase 4)

Implement pedersen_commit, zero_commit, and gen_commitment_mask in Rust
  with hardcoded H generator point. All 56 equivalence tests pass including
  homomorphic property verification: commit(a,m) - zeroCommit(a) = m*G.
This commit is contained in:
Matt Hess
2026-02-01 01:24:43 +00:00
parent 638171efe1
commit 0bf0c9e4b3
7 changed files with 775 additions and 0 deletions
+15
View File
@@ -17,6 +17,9 @@ import {
scalarMultBase, scalarMultPoint, pointAddCompressed,
pointSubCompressed, pointNegate, doubleScalarMultBase
} from '../ed25519.js';
import { hashToPoint, generateKeyImage } from '../keyimage.js';
import { generateKeyDerivation, derivePublicKey, deriveSecretKey } from '../scanning.js';
import { commit, zeroCommit, genCommitmentMask } from '../transaction/serialization.js';
export class JsCryptoBackend {
constructor() {
@@ -60,4 +63,16 @@ export class JsCryptoBackend {
const bG = scalarMultBase(b);
return pointAddCompressed(aP, bG);
}
// Hash-to-point & key derivation
hashToPoint(data) { return hashToPoint(data); }
generateKeyImage(pubKey, secKey) { return generateKeyImage(pubKey, secKey); }
generateKeyDerivation(pubKey, secKey) { return generateKeyDerivation(pubKey, secKey); }
derivePublicKey(derivation, outputIndex, basePub) { return derivePublicKey(derivation, outputIndex, basePub); }
deriveSecretKey(derivation, outputIndex, baseSec) { return deriveSecretKey(derivation, outputIndex, baseSec); }
// Pedersen commitments
commit(amount, mask) { return commit(amount, mask); }
zeroCommit(amount) { return zeroCommit(amount); }
genCommitmentMask(sharedSecret) { return genCommitmentMask(sharedSecret); }
}
+48
View File
@@ -76,4 +76,52 @@ export class WasmCryptoBackend {
pointSubCompressed(p, q) { return this.wasm.point_sub_compressed(p, q); }
pointNegate(p) { return this.wasm.point_negate(p); }
doubleScalarMultBase(a, p, b) { return this.wasm.double_scalar_mult_base(a, p, b); }
// Hash-to-point & key derivation
hashToPoint(data) { return this.wasm.hash_to_point(data); }
generateKeyImage(pubKey, secKey) { return this.wasm.generate_key_image(pubKey, secKey); }
generateKeyDerivation(pubKey, secKey) { return this.wasm.generate_key_derivation(pubKey, secKey); }
derivePublicKey(derivation, outputIndex, basePub) { return this.wasm.derive_public_key(derivation, outputIndex, basePub); }
deriveSecretKey(derivation, outputIndex, baseSec) { return this.wasm.derive_secret_key(derivation, outputIndex, baseSec); }
// Pedersen commitments
commit(amount, mask) {
// Convert amount (BigInt/number) to 32-byte LE scalar
let amountBytes = amount;
if (typeof amount === 'bigint' || typeof amount === 'number') {
let n = BigInt(amount);
amountBytes = new Uint8Array(32);
for (let i = 0; i < 32 && n > 0n; i++) {
amountBytes[i] = Number(n & 0xffn);
n >>= 8n;
}
}
// Convert mask if hex string
if (typeof mask === 'string') {
const hex = mask;
mask = new Uint8Array(hex.length / 2);
for (let i = 0; i < mask.length; i++) mask[i] = parseInt(hex.substr(i*2, 2), 16);
}
return this.wasm.pedersen_commit(amountBytes, mask);
}
zeroCommit(amount) {
let amountBytes = amount;
if (typeof amount === 'bigint' || typeof amount === 'number') {
let n = BigInt(amount);
amountBytes = new Uint8Array(32);
for (let i = 0; i < 32 && n > 0n; i++) {
amountBytes[i] = Number(n & 0xffn);
n >>= 8n;
}
}
return this.wasm.zero_commit(amountBytes);
}
genCommitmentMask(sharedSecret) {
if (typeof sharedSecret === 'string') {
const hex = sharedSecret;
sharedSecret = new Uint8Array(hex.length / 2);
for (let i = 0; i < sharedSecret.length; i++) sharedSecret[i] = parseInt(hex.substr(i*2, 2), 16);
}
return this.wasm.gen_commitment_mask(sharedSecret);
}
}
+3
View File
@@ -18,6 +18,9 @@ export {
scReduce32, scReduce64, scInvert, scCheck, scIsZero,
scalarMultBase, scalarMultPoint, pointAddCompressed,
pointSubCompressed, pointNegate, doubleScalarMultBase,
hashToPoint, generateKeyImage, generateKeyDerivation,
derivePublicKey, deriveSecretKey,
commit, zeroCommit, genCommitmentMask,
} from './provider.js';
// Backends (for direct access / testing)
+12
View File
@@ -88,3 +88,15 @@ export function pointAddCompressed(p, q) { return getCryptoBackend().pointAddCom
export function pointSubCompressed(p, q) { return getCryptoBackend().pointSubCompressed(p, q); }
export function pointNegate(p) { return getCryptoBackend().pointNegate(p); }
export function doubleScalarMultBase(a, p, b) { return getCryptoBackend().doubleScalarMultBase(a, p, b); }
// Hash-to-point & key derivation
export function hashToPoint(data) { return getCryptoBackend().hashToPoint(data); }
export function generateKeyImage(pubKey, secKey) { return getCryptoBackend().generateKeyImage(pubKey, secKey); }
export function generateKeyDerivation(pubKey, secKey) { return getCryptoBackend().generateKeyDerivation(pubKey, secKey); }
export function derivePublicKey(derivation, outputIndex, basePub) { return getCryptoBackend().derivePublicKey(derivation, outputIndex, basePub); }
export function deriveSecretKey(derivation, outputIndex, baseSec) { return getCryptoBackend().deriveSecretKey(derivation, outputIndex, baseSec); }
// Pedersen commitments
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); }