UNPKG

liquidjs-lib

Version:

Client-side Liquid JavaScript library

460 lines (459 loc) 15.3 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.Blinder = void 0; const asset_1 = require('../asset'); const issuance_1 = require('../issuance'); const transaction_1 = require('../transaction'); class Blinder { constructor(pset, ownedInputs, validator, generator) { if (ownedInputs.length === 0) { throw new Error('Missing owned inputs'); } pset.sanityCheck(); this.pset = pset; this.ownedInputs = ownedInputs; this.blindingValidator = validator; this.blindingGenerator = generator; } blindNonLast(args) { this.blind(args, false); } blindLast(args) { this.blind(args, true); } blind(args, lastBlinder) { if (this.pset.isFullyBlinded()) { return; } const { issuanceBlindingArgs, outputBlindingArgs } = args; if (outputBlindingArgs.length === 0) { throw new Error('missing outputs blinding args'); } if (issuanceBlindingArgs && issuanceBlindingArgs.length > 0) { issuanceBlindingArgs.forEach((arg) => this.validateIssuanceBlindingArgs(arg), ); } const sortedOutputBlindingArgs = outputBlindingArgs.sort( (a, b) => a.index - b.index, ); sortedOutputBlindingArgs.forEach((arg, i) => { const isLastBlinder = lastBlinder && i === sortedOutputBlindingArgs.length - 1; this.validateOutputBlindingArgs(arg, isLastBlinder); }); this.validateBlindingData(lastBlinder, outputBlindingArgs); const inputScalar = this.calculateInputScalar(issuanceBlindingArgs); const outputScalar = this.calculateOutputScalar(sortedOutputBlindingArgs); const pset = this.pset.copy(); if (issuanceBlindingArgs) { issuanceBlindingArgs.forEach( ({ index, issuanceValueCommitment, issuanceValueRangeProof, issuanceValueBlindProof, issuanceTokenCommitment, issuanceTokenRangeProof, issuanceTokenBlindProof, }) => { pset.inputs[index].issuanceValueCommitment = issuanceValueCommitment; pset.inputs[index].issuanceValueRangeproof = issuanceValueRangeProof; pset.inputs[index].issuanceBlindValueProof = issuanceValueBlindProof; pset.inputs[index].issuanceInflationKeysCommitment = issuanceTokenCommitment; pset.inputs[index].issuanceInflationKeysRangeproof = issuanceTokenRangeProof; pset.inputs[index].issuanceBlindInflationKeysProof = issuanceTokenBlindProof; }, ); } for (let i = 0; i < sortedOutputBlindingArgs.length; i++) { const { index, assetBlinder, assetCommitment, assetSurjectionProof, assetBlindProof, valueBlinder, nonceCommitment, nonce, } = sortedOutputBlindingArgs[i]; let { valueCommitment, valueRangeProof, valueBlindProof } = sortedOutputBlindingArgs[i]; const targetOutput = pset.outputs[index]; const value = targetOutput.value.toString(10); if (lastBlinder && i === sortedOutputBlindingArgs.length - 1) { const lastValueBlinder = this.calculateLastValueBlinder( valueBlinder, outputScalar, inputScalar, ); valueCommitment = this.blindingGenerator.lastValueCommitment( value, assetCommitment, lastValueBlinder, ); valueRangeProof = this.blindingGenerator.lastValueRangeProof( value, targetOutput.asset, valueCommitment, assetCommitment, lastValueBlinder, assetBlinder, nonce, targetOutput.script || Buffer.alloc(0), ); valueBlindProof = this.blindingGenerator.lastBlindValueProof( value, valueCommitment, assetCommitment, lastValueBlinder, ); } pset.outputs[index].valueCommitment = valueCommitment; pset.outputs[index].valueRangeproof = valueRangeProof; pset.outputs[index].blindValueProof = valueBlindProof; pset.outputs[index].assetCommitment = assetCommitment; pset.outputs[index].assetSurjectionProof = assetSurjectionProof; pset.outputs[index].blindAssetProof = assetBlindProof; pset.outputs[index].ecdhPubkey = nonceCommitment; pset.outputs[index].blinderIndex = undefined; } if (!lastBlinder) { if (!pset.globals.scalars) { pset.globals.scalars = []; } pset.globals.scalars.push(outputScalar); } else { pset.globals.scalars = undefined; } pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; } calculateInputScalar(issuanceBlindingArgs) { let scalar = Buffer.from(transaction_1.ZERO); for (const input of this.ownedInputs) { scalar = this.blindingGenerator.computeAndAddToScalarOffset( scalar, input.value, input.assetBlindingFactor, input.valueBlindingFactor, ); const pInput = this.pset.inputs[input.index]; if (pInput.hasIssuance() || pInput.hasReissuance()) { const issuance = issuanceBlindingArgs && issuanceBlindingArgs.find(({ index }) => index === input.index); if (issuance) { const valueBlinder = issuance.issuanceValueBlinder && issuance.issuanceValueBlinder.length > 0 ? issuance.issuanceValueBlinder : transaction_1.ZERO; scalar = this.blindingGenerator.computeAndAddToScalarOffset( scalar, pInput.issuanceValue ? pInput.issuanceValue.toString(10) : '0', transaction_1.ZERO, valueBlinder, ); if (pInput.issuanceInflationKeys > 0) { const tokenBlinder = issuance.issuanceTokenBlinder && issuance.issuanceTokenBlinder.length > 0 ? issuance.issuanceTokenBlinder : transaction_1.ZERO; scalar = this.blindingGenerator.computeAndAddToScalarOffset( scalar, pInput.issuanceInflationKeys.toString(10), transaction_1.ZERO, tokenBlinder, ); } } } } return scalar; } calculateOutputScalar(outputBlindingArgs) { let scalar = Buffer.from(transaction_1.ZERO); for (const args of outputBlindingArgs) { const output = this.pset.outputs[args.index]; scalar = this.blindingGenerator.computeAndAddToScalarOffset( scalar, output.value.toString(10), args.assetBlinder, args.valueBlinder, ); } return scalar; } calculateLastValueBlinder(valueBlinder, outputScalar, inputScalar) { const offset = this.blindingGenerator.subtractScalars( outputScalar, inputScalar, ); let lastValueBlinder = this.blindingGenerator.subtractScalars( valueBlinder, offset, ); if (this.pset.globals.scalars) { for (const scalar of this.pset.globals.scalars) { lastValueBlinder = this.blindingGenerator.subtractScalars( lastValueBlinder, scalar, ); } } return lastValueBlinder; } validateIssuanceBlindingArgs(args) { const { index, issuanceAsset, issuanceToken, issuanceValueCommitment, issuanceTokenCommitment, issuanceValueRangeProof, issuanceTokenRangeProof, issuanceValueBlindProof, issuanceTokenBlindProof, issuanceValueBlinder, issuanceTokenBlinder, } = args; if (index < 0 || index >= this.pset.globals.inputCount) { throw new Error('Input index out of range'); } const targetInput = this.pset.inputs[index]; if (!targetInput.hasIssuance() && !targetInput.hasReissuance()) { throw new Error('Missing issuance on target input'); } if (issuanceAsset.length === 0) { throw new Error('Missing issuance asset'); } if (issuanceAsset.length !== 32) { throw new Error('Invalid issuance asset length'); } if (issuanceValueCommitment.length === 0) { throw new Error('Missing issuance value commitment'); } if (issuanceValueCommitment.length !== 33) { throw new Error('Invalid issuance value commitment length'); } if (issuanceValueBlinder.length === 0) { throw new Error('Missing issuance value blinder'); } if (issuanceValueBlinder.length !== 32) { throw new Error('Invalid issuance value blinder length'); } if (issuanceValueRangeProof.length === 0) { throw new Error('Missing issuance value range proof'); } if (issuanceValueBlindProof.length === 0) { throw new Error('Missing issuance blind value proof'); } if (targetInput.issuanceInflationKeys > 0) { if (issuanceToken.length === 0) { throw new Error('Missing issuance token'); } if (issuanceToken.length !== 32) { throw new Error('Invalid issuance token length'); } if (issuanceTokenCommitment.length === 0) { throw new Error('Missing issuance token commitment'); } if (issuanceTokenCommitment.length !== 33) { throw new Error('Invalid issuance token commitment length'); } if (issuanceTokenBlinder.length === 0) { throw new Error('Missing issuance token blinder'); } if (issuanceTokenBlinder.length !== 32) { throw new Error('Invalid issuance token blinder length'); } if (issuanceTokenRangeProof.length === 0) { throw new Error('Missing issuance token range proof'); } if (issuanceTokenBlindProof.length === 0) { throw new Error('Missing issuance blind token value proof'); } } } async validateOutputBlindingArgs(args, lastBlinder) { const { index, nonce, nonceCommitment, valueCommitment, assetCommitment, valueRangeProof, assetSurjectionProof, valueBlindProof, assetBlindProof, valueBlinder, assetBlinder, } = args; if (index < 0 || index >= this.pset.globals.outputCount) { throw new Error('Output index out of range'); } const targetOutput = this.pset.outputs[index]; if (!targetOutput.needsBlinding()) { throw new Error( 'Target output does not need blinding (does not have a blinding pubkey set)', ); } if (!this.ownOutput(targetOutput.blinderIndex)) { throw new Error('Output is not owned by this blinder'); } if (nonce.length === 0) { throw new Error('Missing nonce'); } if (nonce.length !== 32) { throw new Error('Invalid nonce length'); } if (nonceCommitment.length === 0) { throw new Error('Missing nonce commitment'); } if (nonceCommitment.length !== 33) { throw new Error('Invalid nonce commitment length'); } if (valueBlinder.length === 0) { throw new Error('Missing value blinder'); } if (valueBlinder.length !== 32) { throw new Error('Invalid value blinder length'); } if (assetBlinder.length === 0) { throw new Error('Missing asset blinder'); } if (assetBlinder.length !== 32) { throw new Error('Invalid asset blinder length'); } if (assetCommitment.length === 0) { throw new Error('Missing asset commitment'); } if (assetCommitment.length !== 33) { throw new Error('Invalid asset commitment length'); } if (assetSurjectionProof.length === 0) { throw new Error('Missing asset surjection proof'); } if (assetBlindProof.length === 0) { throw new Error('Missing blind asset proof'); } if (!lastBlinder) { if (valueCommitment.length === 0) { throw new Error('Missing value commitment'); } if (valueCommitment.length !== 33) { throw new Error('Invalid value commitment length'); } if (valueRangeProof.length === 0) { throw new Error('Missing value range proof'); } if (valueBlindProof.length === 0) { throw new Error('Missing blind value proof'); } } } ownOutput(blinderIndex) { return ( this.ownedInputs.find(({ index }) => index === blinderIndex) !== undefined ); } validateBlindingData(isLastBlinder, outputBlindingArgs) { const inAssetsAndBlinders = this.pset.inputs.map((input, i) => { const ownedInput = this.ownedInputs.find(({ index }) => index === i); return ownedInput ? { asset: ownedInput.asset, assetBlinder: ownedInput.assetBlindingFactor, } : { asset: asset_1.AssetHash.fromBytes(input.getUtxo().asset) .bytesWithoutPrefix, assetBlinder: transaction_1.ZERO, }; }); for (const input of this.pset.inputs) { if (input.hasIssuance() || input.hasReissuance()) { inAssetsAndBlinders.push({ asset: input.getIssuanceAssetHash(), assetBlinder: transaction_1.ZERO, }); if (input.issuanceInflationKeys > 0) { const entropy = input.getIssuanceEntropy(); const token = (0, issuance_1.calculateReissuanceToken)( entropy, input.blindedIssuance ?? true, ); inAssetsAndBlinders.push({ asset: token, assetBlinder: transaction_1.ZERO, }); } } } const inputAssets = inAssetsAndBlinders.map((v) => v.asset); const inputAssetBlinders = inAssetsAndBlinders.map((v) => v.assetBlinder); for (let i = 0; i < outputBlindingArgs.length; i++) { const { index, assetBlinder, assetCommitment, assetSurjectionProof, assetBlindProof, valueCommitment, valueRangeProof, valueBlindProof, } = outputBlindingArgs[i]; const targetOutput = this.pset.outputs[index]; const lastBlinder = isLastBlinder && i === outputBlindingArgs.length - 1; if ( !this.blindingValidator.verifyAssetSurjectionProof( inputAssets, inputAssetBlinders, targetOutput.asset, assetBlinder, assetSurjectionProof, ) ) { throw new Error('Invalid output asset surjection proof'); } if ( !this.blindingValidator.verifyBlindAssetProof( targetOutput.asset, assetCommitment, assetBlindProof, ) ) { throw new Error('Invalid output asset blind proof'); } if (!lastBlinder) { if ( !this.blindingValidator.verifyValueRangeProof( valueRangeProof, valueCommitment, assetCommitment, targetOutput.script, ) ) { throw new Error('Invalid output value range proof'); } if ( !this.blindingValidator.verifyBlindValueProof( valueBlindProof, valueCommitment, assetCommitment, ) ) { throw new Error('Invalid output value blind proof'); } } } } } exports.Blinder = Blinder;