Skip to content

Commit

Permalink
Bump packed. Fix test imports in node v22
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmillr committed May 16, 2024
1 parent 07272a3 commit 9b5d823
Show file tree
Hide file tree
Showing 13 changed files with 52 additions and 46 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
"name": "@scure/btc-signer",
"version": "1.3.1",
"description": "Audited & minimal library for creating, signing & decoding Bitcoin transactions: with Schnorr, Taproot, UTXO & PSBT",
"files": [
"lib",
"src"
],
"files": ["lib", "src"],
"main": "lib/index.js",
"module": "lib/esm/index.js",
"types": "lib/index.d.ts",
Expand Down Expand Up @@ -55,7 +52,7 @@
"@noble/curves": "~1.4.0",
"@noble/hashes": "~1.4.0",
"@scure/base": "~1.1.6",
"micro-packed": "~0.5.3"
"micro-packed": "~0.6.2"
},
"devDependencies": {
"@paulmillr/jsbt": "0.1.0",
Expand Down
10 changes: 5 additions & 5 deletions src/payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ export type OutScriptType = typeof OutScript;

// Basic sanity check for scripts
function checkWSH(s: OutWSHType, witnessScript: Bytes) {
if (!P.equalBytes(s.hash, u.sha256(witnessScript)))
if (!u.equalBytes(s.hash, u.sha256(witnessScript)))
throw new Error('checkScript: wsh wrong witnessScript hash');
const w = OutScript.decode(witnessScript);
if (w.type === 'tr' || w.type === 'tr_ns' || w.type === 'tr_ms')
Expand All @@ -274,7 +274,7 @@ export function checkScript(script?: Bytes, redeemScript?: Bytes, witnessScript?
if (s.type === 'tr_ns' || s.type === 'tr_ms' || s.type === 'ms' || s.type == 'pk')
throw new Error(`checkScript: non-wrapped ${s.type}`);
if (s.type === 'sh' && redeemScript) {
if (!P.equalBytes(s.hash, u.hash160(redeemScript)))
if (!u.equalBytes(s.hash, u.hash160(redeemScript)))
throw new Error('checkScript: sh wrong redeemScript hash');
const r = OutScript.decode(redeemScript);
if (r.type === 'tr' || r.type === 'tr_ns' || r.type === 'tr_ms')
Expand Down Expand Up @@ -390,7 +390,7 @@ function checkTaprootScript(
const outms = out as OutTRNSType | OutTRMSType;
if (!allowUnknownOutputs && outms.pubkeys) {
for (const p of outms.pubkeys) {
if (P.equalBytes(p, u.TAPROOT_UNSPENDABLE_KEY))
if (u.equalBytes(p, u.TAPROOT_UNSPENDABLE_KEY))
throw new Error('Unspendable taproot key in leaf script');
// It's likely a mistake at this point:
// 1. p2tr(A, p2tr_ns(2, [A, B])) == p2tr(A, p2tr_pk(B)) (A or B key)
Expand All @@ -399,7 +399,7 @@ function checkTaprootScript(
// User creates 2 of 3 multisig of keys [A, B, C],
// but key A always can spend whole output without signatures from other keys.
// p2tr(A, p2tr_ns(2, [B, C, D])) is ok: A or (B and C) or (B and D) or (C and D)
if (P.equalBytes(p, internalPubKey)) {
if (u.equalBytes(p, internalPubKey)) {
throw new Error(
'Using P2TR with leaf script with same key as internal key is not supported'
);
Expand Down Expand Up @@ -500,7 +500,7 @@ function taprootHashTree(
if (!Array.isArray(tree)) {
const { leafVersion: version, script: leafScript } = tree;
// Earliest tree walk where we can validate tapScripts
if (tree.tapLeafScript || (tree.tapMerkleRoot && !P.equalBytes(tree.tapMerkleRoot, P.EMPTY)))
if (tree.tapLeafScript || (tree.tapMerkleRoot && !u.equalBytes(tree.tapMerkleRoot, P.EMPTY)))
throw new Error('P2TR: tapRoot leafScript cannot have tree');
const script = typeof leafScript === 'string' ? hex.decode(leafScript) : leafScript;
if (!u.isBytes(script)) throw new Error(`checkScript: wrong script type=${script}`);
Expand Down
7 changes: 3 additions & 4 deletions src/psbt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { hex } from '@scure/base';
import * as P from 'micro-packed';
import { CompactSize, CompactSizeLen, RawOutput, RawTx, RawWitness, VarBytes } from './script.js';
import { Transaction } from './transaction.js'; // circular
import { Bytes, compareBytes, PubT, validatePubkey } from './utils.js';
import { Bytes, compareBytes, PubT, validatePubkey, equalBytes } from './utils.js';

// PSBT BIP174, BIP370, BIP371

Expand Down Expand Up @@ -272,8 +272,7 @@ export const PSBTInputCoder = P.validate(PSBTKeyMap(PSBTInput), (i) => {
const prevOut = i.nonWitnessUtxo.outputs[i.index];
if (
i.witnessUtxo &&
(!P.equalBytes(i.witnessUtxo.script, prevOut.script) ||
i.witnessUtxo.amount !== prevOut.amount)
(!equalBytes(i.witnessUtxo.script, prevOut.script) || i.witnessUtxo.amount !== prevOut.amount)
)
throw new Error('validateInput: witnessUtxo different from nonWitnessUtxo');
}
Expand Down Expand Up @@ -499,7 +498,7 @@ export function mergeKeyMap<T extends PSBTKeyMap>(
} else if (typeof res[k] === 'string') {
res[k] = vC.decode(hex.decode(res[k] as string));
} else if (cannotChange && k in val && cur && cur[k] !== undefined) {
if (!P.equalBytes(vC.encode(val[k]), vC.encode(cur[k])))
if (!equalBytes(vC.encode(val[k]), vC.encode(cur[k])))
throw new Error(`Cannot change signed field=${k}`);
}
}
Expand Down
12 changes: 7 additions & 5 deletions src/script.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as P from 'micro-packed';
import { isBytes } from './utils.js';

export const MAX_SCRIPT_BYTE_LENGTH = 520;

Expand Down Expand Up @@ -57,10 +58,11 @@ export function ScriptNum(bytesLimit = 6, forceMinimal = false): P.CoderType<big
throw new Error(`ScriptNum: number (${len}) bigger than limit=${bytesLimit}`);
if (len === 0) return 0n;
if (forceMinimal) {
const data = r.bytes(len, true);
// MSB is zero (without sign bit) -> not minimally encoded
if ((r.data[len - 1] & 0x7f) === 0) {
if ((data[data.length - 1] & 0x7f) === 0) {
// exception
if (len <= 1 || (r.data[len - 2] & 0x80) === 0)
if (len <= 1 || (data[data.length - 2] & 0x80) === 0)
throw new Error('Non-minimally encoded ScriptNum');
}
}
Expand All @@ -81,7 +83,7 @@ export function ScriptNum(bytesLimit = 6, forceMinimal = false): P.CoderType<big

export function OpToNum(op: ScriptOP, bytesLimit = 4, forceMinimal = true) {
if (typeof op === 'number') return op;
if (P.isBytes(op)) {
if (isBytes(op)) {
try {
const val = ScriptNum(bytesLimit, forceMinimal).decode(op);
if (val > Number.MAX_SAFE_INTEGER) return;
Expand Down Expand Up @@ -120,7 +122,7 @@ export const Script: P.CoderType<ScriptType> = P.wrap({
}
// Encode big numbers
if (typeof o === 'number') o = ScriptNum().encode(BigInt(o));
if (!P.isBytes(o)) throw new Error(`Wrong Script OP=${o} (${typeof o})`);
if (!isBytes(o)) throw new Error(`Wrong Script OP=${o} (${typeof o})`);
// Bytes
const len = o.length;
if (len < OP.PUSHDATA1) w.byte(len);
Expand Down Expand Up @@ -195,7 +197,7 @@ export const CompactSize: P.CoderType<bigint> = P.wrap({
});

// Same thing, but in number instead of bigint. Checks for safe integer inside
export const CompactSizeLen = P.apply(CompactSize, P.coders.number);
export const CompactSizeLen = P.apply(CompactSize, P.coders.numberBigint);

// ui8a of size <CompactSize>
export const VarBytes = P.bytes(CompactSize);
Expand Down
25 changes: 12 additions & 13 deletions src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { hex } from '@scure/base';
import { Address, CustomScript, OutScript, checkScript, tapLeafHash } from './payment.js';
import * as psbt from './psbt.js'; // circular
import { CompactSizeLen, RawOutput, RawTx, RawWitness, Script, VarBytes } from './script.js';
import { NETWORK, Bytes, concatBytes, isBytes } from './utils.js';
import { NETWORK, Bytes, concatBytes, isBytes, equalBytes } from './utils.js';
import * as u from './utils.js';
import { getInputType, toVsize, normalizeInput, getPrevOut } from './utxo.js'; // circular

Expand Down Expand Up @@ -108,7 +108,7 @@ function getTaprootKeys(
internalKey: Bytes,
merkleRoot: Bytes = P.EMPTY
) {
if (P.equalBytes(internalKey, pubKey)) {
if (equalBytes(internalKey, pubKey)) {
privKey = u.taprootTweakPrivKey(privKey, merkleRoot);
pubKey = u.pubSchnorr(privKey);
}
Expand Down Expand Up @@ -700,7 +700,7 @@ export class Transaction {
.map(([pubKey, { path }]) => {
let s = privateKey as HDKey;
for (const i of path) s = s.deriveChild(i);
if (!P.equalBytes(s.publicKey, pubKey)) throw new Error('bip32Derivation: wrong pubKey');
if (!equalBytes(s.publicKey, pubKey)) throw new Error('bip32Derivation: wrong pubKey');
if (!s.privateKey) throw new Error('bip32Derivation: no privateKey');
return s;
});
Expand Down Expand Up @@ -754,7 +754,7 @@ export class Transaction {
merkleRoot
);
const [taprootPubKey, _] = u.taprootTweakPubkey(input.tapInternalKey, merkleRoot);
if (P.equalBytes(taprootPubKey, pubKey)) {
if (equalBytes(taprootPubKey, pubKey)) {
const hash = this.preimageWitnessV1(idx, prevOutScript, sighash, amount);
const sig = concatBytes(
u.signSchnorr(hash, privKey, _auxRand),
Expand All @@ -772,7 +772,7 @@ export class Transaction {
const ver = _script[_script.length - 1];
const hash = tapLeafHash(script, ver);
// NOTE: no need to tweak internal key here, since we don't support nested p2tr
const pos = scriptDecoded.findIndex((i) => isBytes(i) && P.equalBytes(i, schnorrPub));
const pos = scriptDecoded.findIndex((i) => isBytes(i) && equalBytes(i, schnorrPub));
// Skip if there is no public key in tapLeafScript
if (pos === -1) continue;
const msg = this.preimageWitnessV1(
Expand Down Expand Up @@ -806,8 +806,7 @@ export class Transaction {
let hasPubkey = false;
const pubKeyHash = u.hash160(pubKey);
for (const i of Script.decode(inputType.lastScript)) {
if (isBytes(i) && (P.equalBytes(i, pubKey) || P.equalBytes(i, pubKeyHash)))
hasPubkey = true;
if (isBytes(i) && (equalBytes(i, pubKey) || equalBytes(i, pubKeyHash))) hasPubkey = true;
}
if (!hasPubkey) throw new Error(`Input script doesn't have pubKey: ${inputType.lastScript}`);
let hash;
Expand Down Expand Up @@ -870,14 +869,14 @@ export class Transaction {
const ver = _script[_script.length - 1];
const outScript = OutScript.decode(script);
const hash = tapLeafHash(script, ver);
const scriptSig = input.tapScriptSig.filter((i) => P.equalBytes(i[0].leafHash, hash));
const scriptSig = input.tapScriptSig.filter((i) => equalBytes(i[0].leafHash, hash));
let signatures: Bytes[] = [];
if (outScript.type === 'tr_ms') {
const m = outScript.m;
const pubkeys = outScript.pubkeys;
let added = 0;
for (const pub of pubkeys) {
const sigIdx = scriptSig.findIndex((i) => P.equalBytes(i[0].pubKey, pub));
const sigIdx = scriptSig.findIndex((i) => equalBytes(i[0].pubKey, pub));
// Should have exact amount of signatures (more -- will fail)
if (added === m || sigIdx === -1) {
signatures.push(P.EMPTY);
Expand All @@ -890,7 +889,7 @@ export class Transaction {
if (added !== m) continue;
} else if (outScript.type === 'tr_ns') {
for (const pub of outScript.pubkeys) {
const sigIdx = scriptSig.findIndex((i) => P.equalBytes(i[0].pubKey, pub));
const sigIdx = scriptSig.findIndex((i) => equalBytes(i[0].pubKey, pub));
if (sigIdx === -1) continue;
signatures.push(scriptSig[sigIdx][1]);
}
Expand All @@ -900,7 +899,7 @@ export class Transaction {
const scriptDecoded = Script.decode(script);
signatures = scriptSig
.map(([{ pubKey }, signature]) => {
const pos = scriptDecoded.findIndex((i) => isBytes(i) && P.equalBytes(i, pubKey));
const pos = scriptDecoded.findIndex((i) => isBytes(i) && equalBytes(i, pubKey));
if (pos === -1)
throw new Error('finalize/taproot: cannot find position of pubkey in script');
return { signature, pos };
Expand Down Expand Up @@ -951,7 +950,7 @@ export class Transaction {
let signatures = [];
// partial: [pubkey, sign]
for (const pub of pubkeys) {
const sign = input.partialSig.find((s) => P.equalBytes(pub, s[0]));
const sign = input.partialSig.find((s) => equalBytes(pub, s[0]));
if (!sign) continue;
signatures.push(sign[1]);
}
Expand Down Expand Up @@ -1024,7 +1023,7 @@ export class Transaction {
}
const thisUnsigned = this.global.unsignedTx ? RawTx.encode(this.global.unsignedTx) : P.EMPTY;
const otherUnsigned = other.global.unsignedTx ? RawTx.encode(other.global.unsignedTx) : P.EMPTY;
if (!P.equalBytes(thisUnsigned, otherUnsigned))
if (!equalBytes(thisUnsigned, otherUnsigned))
throw new Error(`Transaction/combine: different unsigned tx`);
this.global = psbt.mergeKeyMap(psbt.PSBTGlobal, this.global, other.global);
for (let i = 0; i < this.inputs.length; i++) this.updateInput(i, other.inputs[i], true);
Expand Down
5 changes: 3 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isBytes, concatBytes, U32LE } from 'micro-packed';
import { utils as packedUtils, U32LE } from 'micro-packed';
import { ripemd160 } from '@noble/hashes/ripemd160';
import { sha256 } from '@noble/hashes/sha256';
import { secp256k1 as secp, schnorr } from '@noble/curves/secp256k1';
Expand All @@ -7,7 +7,8 @@ export type Bytes = Uint8Array;
const Point = secp.ProjectivePoint;
const CURVE_ORDER = secp.CURVE.n;

export { sha256, isBytes, concatBytes };
const { isBytes, concatBytes, equalBytes } = packedUtils;
export { sha256, isBytes, concatBytes, equalBytes };

export const hash160 = (msg: Bytes) => ripemd160(sha256(msg));
export const sha256x2 = (...msgs: Bytes[]) => sha256(sha256(concatBytes(...msgs)));
Expand Down
14 changes: 11 additions & 3 deletions src/utxo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@ import {
SignatureHash,
Transaction,
} from './transaction.js'; // circular
import { NETWORK, Bytes, compareBytes, isBytes, TAPROOT_UNSPENDABLE_KEY, sha256 } from './utils.js';
import {
NETWORK,
Bytes,
compareBytes,
equalBytes,
isBytes,
TAPROOT_UNSPENDABLE_KEY,
sha256,
} from './utils.js';
import { validatePubkey, PubT } from './utils.js';

// Normalizes input
Expand Down Expand Up @@ -153,7 +161,7 @@ function iterLeafs(tapLeafScript: TapLeafScript, sigSize: number, customScripts?
const csEncoded = c.encode(scriptDecoded);
if (csEncoded === undefined) continue;
const pubKeys = scriptDecoded.filter((i) => {
if (!P.isBytes(i)) return false;
if (!isBytes(i)) return false;
try {
validatePubkey(i, PubT.schnorr);
return true;
Expand Down Expand Up @@ -187,7 +195,7 @@ function estimateInput(
// schnorr sig is always 64 bytes. except for cases when sighash is not default!
if (inputType.txType === 'taproot') {
const SCHNORR_SIG_SIZE = inputType.sighash !== SignatureHash.DEFAULT ? 65 : 64;
if (input.tapInternalKey && !P.equalBytes(input.tapInternalKey, TAPROOT_UNSPENDABLE_KEY)) {
if (input.tapInternalKey && !equalBytes(input.tapInternalKey, TAPROOT_UNSPENDABLE_KEY)) {
witness = [new Uint8Array(SCHNORR_SIG_SIZE)];
} else if (input.tapLeafScript) {
witness = iterLeafs(input.tapLeafScript, SCHNORR_SIG_SIZE, opts.customScripts);
Expand Down
2 changes: 1 addition & 1 deletion test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ should('Script', () => {
hex.decode(
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
)
).map((i) => (P.isBytes(i) ? hex.encode(i) : i)),
).map((i) => (P.utils.isBytes(i) ? hex.encode(i) : i)),
[
2,
'030000000000000000000000000000000000000000000000000000000000000001',
Expand Down
2 changes: 1 addition & 1 deletion test/bip340-schnorr.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { deepStrictEqual } from 'node:assert';
import { should } from 'micro-should';
import { hex } from '@scure/base';
import { schnorr } from '@noble/curves/secp256k1';
import { default as v340 } from './fixtures/bip340.json' assert { type: 'json' };
import { default as v340 } from './fixtures/bip340.json' with { type: 'json' };

// BIP340 (same as in secp256k1, just to be sure)
for (const v of v340) {
Expand Down
2 changes: 1 addition & 1 deletion test/bip341-taproot.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { deepStrictEqual } from 'node:assert';
import { should } from 'micro-should';
import { hex } from '@scure/base';
import * as btc from '../lib/esm/index.js';
import { default as v341 } from './fixtures/bip341.json' assert { type: 'json' };
import { default as v341 } from './fixtures/bip341.json' with { type: 'json' };
import { schnorr } from '@noble/curves/secp256k1';

for (let i = 0; i < v341.keyPathSpending.length; i++) {
Expand Down
2 changes: 1 addition & 1 deletion test/psbt-test/bip174-psbt-extended.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { deepStrictEqual, throws } from 'node:assert';
import { describe, should } from 'micro-should';
import { hex, base64 } from '@scure/base';
import * as btc from '../../lib/esm/index.js';
import { default as rpcPSBT } from './fixtures/rpc_psbt.json' assert { type: 'json' };
import { default as rpcPSBT } from './fixtures/rpc_psbt.json' with { type: 'json' };

describe('bip174-psbt-extended', () => {
for (let i = 0; i < rpcPSBT.invalid.length; i++) {
Expand Down
2 changes: 1 addition & 1 deletion test/utxo-select.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ describe('UTXO Select', () => {
if (signatures.length !== 1)
throw new Error('tr_ord_reveal/finalize: wrong signatures array');
const [{ pubKey }, sig] = signatures[0];
if (!P.equalBytes(pubKey, parsed.pubkey)) return;
if (!P.utils.equalBytes(pubKey, parsed.pubkey)) return;
return [sig, script];
},
},
Expand Down

0 comments on commit 9b5d823

Please sign in to comment.