Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions client/src/driver/blinding.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ function parseBlinders(str) {
}

function verifyNum(num) {
if (!+num) throw new Error('Invalid blinding data (invalid number)')
return +num
if (typeof num != 'string' || !/^[1-9][0-9]*$/.test(num)) throw new Error('Invalid blinding data (invalid number)')
return BigInt(num)
}
function verifyHex32(str) {
if (!str || !/^[0-9a-f]{64}$/i.test(str)) throw new Error('Invalid blinding data (invalid hex)')
Expand Down
14 changes: 11 additions & 3 deletions client/src/lib/deduce-blinded.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ export function deduceBlinded(tx) {
const totals = new Map
tx.vin.filter(vin => vin.prevout && vin.prevout.value != null)
.forEach(({ prevout }) =>
totals.set(prevout.asset, (totals.get(prevout.asset) || 0) + prevout.value))
totals.set(prevout.asset, addAmounts(totals.get(prevout.asset) || 0, prevout.value)))
tx.vout.filter(vout => vout.value != null)
.forEach(vout =>
totals.set(vout.asset, (totals.get(vout.asset) || 0) - vout.value))
totals.set(vout.asset, addAmounts(totals.get(vout.asset) || 0, negateAmount(vout.value))))

// There should only be a single asset where the inputs and outputs amounts mismatch,
// which is the asset of the blinded input/output
Expand All @@ -35,7 +35,15 @@ export function deduceBlinded(tx) {
} else {
if (!unknown_ins.length) throw new Error('expected unknown input')
unknown_ins[0].prevout.asset = blinded_asset
unknown_ins[0].prevout.value = blinded_value * -1
unknown_ins[0].prevout.value = negateAmount(blinded_value)
}
}
}

const addAmounts = (a, b) =>
typeof a == 'bigint' || typeof b == 'bigint'
? BigInt(a) + BigInt(b)
: a + b

const negateAmount = value =>
typeof value == 'bigint' ? -value : value * -1
20 changes: 10 additions & 10 deletions client/src/lib/libwally.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const WALLY_OK = 0
, ASSET_GENERATOR_LEN = 33
, ASSET_TAG_LEN = 32
, BLINDING_FACTOR_LEN = 32
, UINT32_MASK = BigInt('0xffffffff')
, UINT64_MAX = BigInt('0xffffffffffffffff')

const STATIC_ROOT = process.env.STATIC_ROOT || ''
, WASM_URL = process.env.LIBWALLY_WASM_URL || `${STATIC_ROOT}libwally/wallycore.js`
Expand Down Expand Up @@ -51,7 +53,7 @@ export function asset_generator_from_bytes(asset, asset_blinder) {
export function asset_value_commitment(value, value_blinder, asset_commitment) {
// Emscripten transforms int64 function arguments into two int32 arguments, see:
// https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-pass-int64-t-and-uint64-t-values-from-js-into-wasm-functions
const [value_lo, value_hi] = split_int52_lo_hi(value)
const [value_lo, value_hi] = split_uint64_lo_hi(value)

const value_commitment_ptr = Module._malloc(ASSET_COMMITMENT_LEN)
checkCode(Module.ccall('wally_asset_value_commitment'
Expand Down Expand Up @@ -79,18 +81,16 @@ function readBytes(ptr, size) {
return bytes
}

// Split a 52-bit JavaScript number into two 32-bits numbers for the low and high bits
// https://stackoverflow.com/a/19274574
function split_int52_lo_hi(i) {
let lo = i | 0
if (lo < 0) lo += 4294967296
// Split a uint64 value into two exact 32-bit numbers for Emscripten's i64 ABI.
function split_uint64_lo_hi(i) {
if (typeof i != 'bigint') throw new Error('not a bigint: ' + i)

let hi = i - lo
hi /= 4294967296
if (i < BigInt(0) || i > UINT64_MAX) throw new Error('not a uint64: ' + i)

if ((hi < 0) || (hi >= 1048576)) throw new Error ("not an int52: "+i)
const lo = Number(i & UINT32_MASK)
, hi = Number((i >> BigInt(32)) & UINT32_MASK)

return [ lo, hi ]
return [ lo, hi ]
}

function encodeHex(bytes) {
Expand Down
68 changes: 53 additions & 15 deletions client/src/lib/privacy-analysis.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,21 @@ export default function getPrivacyAnalysis(tx) {

// if the transaction could've avoided the smallest input and still have enough to fund
// any of the two outputs, the transaction has what appears to be an unnecessary input.
const minusSmallestIn = sumInputs(tx.vin) - smallestInput(tx.vin)
, largeOut = Math.max(o1.value, o2.value)
, smallOut = Math.min(o1.value, o2.value)

if (minusSmallestIn >= largeOut + tx.fee) {
// UIH2: if it still covers the larger output and fee, this implies this was
// a non-standard transaction that added extra inputs for exotic reasons
detected.push('exotic-detection-uih2')
} else if (minusSmallestIn >= smallOut + tx.fee) {
// UIH1: if it still covers the small output and fee, this implies the smaller
// output was the change and not the payment
detected.push('change-detection-uih1')
const smallestIn = smallestInput(tx.vin)
if (smallestIn != null) {
const minusSmallestIn = subtractAmounts(sumInputs(tx.vin), smallestIn)
, largeOut = maxAmount(o1.value, o2.value)
, smallOut = minAmount(o1.value, o2.value)

if (minusSmallestIn >= addAmounts(largeOut, tx.fee)) {
// UIH2: if it still covers the larger output and fee, this implies this was
// a non-standard transaction that added extra inputs for exotic reasons
detected.push('exotic-detection-uih2')
} else if (minusSmallestIn >= addAmounts(smallOut, tx.fee)) {
// UIH1: if it still covers the small output and fee, this implies the smaller
// output was the change and not the payment
detected.push('change-detection-uih1')
}
}
}
}
Expand Down Expand Up @@ -92,8 +95,39 @@ export default function getPrivacyAnalysis(tx) {

// Utilities

const sumInputs = ins => ins.reduce((T, vin) => T + (vin.prevout && vin.prevout.value || 0), 0)
, smallestInput = ins => Math.min(...ins.map(vin => vin.prevout && vin.prevout.value || Math.Infinity))
const sumInputs = ins => ins.reduce((T, vin) =>
addAmounts(T, vin.prevout && vin.prevout.value != null ? vin.prevout.value : 0), 0)

const smallestInput = ins => ins.reduce((smallest, vin) => {
if (!vin.prevout || vin.prevout.value == null) return smallest
return smallest == null ? vin.prevout.value : minAmount(smallest, vin.prevout.value)
}, null)

const addAmounts = (a, b) =>
Comment thread
FedOken marked this conversation as resolved.
hasBigInt(a, b) ? toBigIntAmount(a) + toBigIntAmount(b) : a + b

const subtractAmounts = (a, b) =>
hasBigInt(a, b) ? toBigIntAmount(a) - toBigIntAmount(b) : a - b

const hasBigInt = (a, b) => typeof a == 'bigint' || typeof b == 'bigint'

const toBigIntAmount = value => BigInt(value == null ? 0 : value)
Comment thread
FedOken marked this conversation as resolved.

const minAmount = (a, b) => {
if (!hasBigInt(a, b)) return a < b ? a : b

const aBig = toBigIntAmount(a)
, bBig = toBigIntAmount(b)
return aBig < bBig ? aBig : bBig
}

const maxAmount = (a, b) => {
if (!hasBigInt(a, b)) return a > b ? a : b

const aBig = toBigIntAmount(a)
, bBig = toBigIntAmount(b)
return aBig > bBig ? aBig : bBig
}

// checks if there's at least one previous output of this type
const inputsHasType = (ins, scriptpubkey_type) =>
Expand All @@ -106,7 +140,11 @@ const lostPrecision = num => {
if (num == 0) return 0;

let count = 0
for (let d=10; num%d==0; ++count, d*=10);
if (typeof num == 'bigint') {
for (let d=BigInt(10); num%d==0; ++count, d*=BigInt(10));
} else {
for (let d=10; num%d==0; ++count, d*=10);
}
return count
}

Expand Down
5 changes: 4 additions & 1 deletion client/src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ export const isRbf = tx => tx.vin.some(vin => vin.sequence < 0xfffffffe)

export const isAllNative = tx => tx.vout.every(isNativeOut)

export const outTotal = tx => tx.vout.reduce((N, vout) => N + (vout.value || 0), 0)
export const outTotal = tx =>
tx.vout.some(vout => typeof vout.value == 'bigint')
? tx.vout.reduce((N, vout) => N + (vout.value == null ? BigInt(0) : BigInt(vout.value)), BigInt(0))
: tx.vout.reduce((N, vout) => N + (vout.value || 0), 0)

export const isNativeOut = vout => (!vout.asset && !vout.assetcommitment) || vout.asset === nativeAssetId

Expand Down