New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@ledgerhq/bitcoin_signer

Package Overview
Dependencies
Maintainers
20
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ledgerhq/bitcoin_signer - npm Package Compare versions

Comparing version 0.1.1 to 0.2.0

2

package.json
{
"name": "@ledgerhq/bitcoin_signer",
"version": "0.1.1",
"version": "0.2.0",
"description": "[Internal development use] INSECURE CLI BTC transaction signer for integration tests",

@@ -5,0 +5,0 @@ "repository": "https://github.com/LedgerHQ/bitcoin_signer",

@@ -1,2 +0,2 @@

import { signTxWithKeyPairs, generateKeychain, toWDBroadcastPayload } from "../lib/sign";
import { SignedTransaction, signTxWithKeyPairs, signTxWithKeyPairsAndTransactionBuilder, generateKeychain, toWDBroadcastPayload } from "../lib/sign";
import { seedFromMnemonic } from "../lib/seeds";

@@ -16,3 +16,3 @@ import { Transaction } from "bitcoinjs-lib";

test("signTxWithKeyPairs signs testnet transaction from seed with derivation", () => {
test("signTxWithKeyPairsAndTransactionBuilder signs testnet transaction from seed with derivation", () => {
const seed = seedFromMnemonic(TEST_SEED, TEST_NETWORK);

@@ -25,7 +25,7 @@

const signedTx = signTxWithKeyPairs(parsed, keyChain, TEST_NETWORK);
const signedTx = signTxWithKeyPairsAndTransactionBuilder(parsed, keyChain, TEST_NETWORK);
expect(signedTx.signed_tx.toHex().length - parsed.toHex().length).toBe(162);
});
test("signTxWithKeyPairs signs testnet transaction from seed with derivation in list", () => {
test("signTxWithKeyPairsAndTransactionBuilder signs testnet transaction from seed with derivation in list", () => {
const seed = seedFromMnemonic(TEST_SEED, TEST_NETWORK);

@@ -38,3 +38,3 @@

const signedTx = signTxWithKeyPairs(parsed, keyChain, TEST_NETWORK);
const signedTx = signTxWithKeyPairsAndTransactionBuilder(parsed, keyChain, TEST_NETWORK);
expect(signedTx.signed_tx.toHex().length - parsed.toHex().length).toBe(162);

@@ -45,3 +45,3 @@ expect(signedTx.pubkey_paths.length).toBe(1);

test("signTxWithKeyPairs signs testnet transaction from seed with derivation not first in list", () => {
test("signTxWithKeyPairsAndTransactionBuilder signs testnet transaction from seed with derivation not first in list", () => {
const seed = seedFromMnemonic(TEST_SEED, TEST_NETWORK);

@@ -54,3 +54,3 @@

const signedTx = signTxWithKeyPairs(parsed, keyChain, TEST_NETWORK);
const signedTx = signTxWithKeyPairsAndTransactionBuilder(parsed, keyChain, TEST_NETWORK);
expect(signedTx.signed_tx.toHex().length - parsed.toHex().length).toBe(162);

@@ -61,3 +61,3 @@ expect(signedTx.pubkey_paths.length).toBe(1);

test("signTxWithKeyPairs fails to sign testnet transaction from seed with wrong derivation", () => {
test("signTxWithKeyPairsAndTransactionBuilder fails to sign testnet transaction from seed with wrong derivation", () => {
const seed = seedFromMnemonic(TEST_SEED, TEST_NETWORK);

@@ -70,3 +70,3 @@

expect(() => signTxWithKeyPairs(parsed, keyChain, TEST_NETWORK)).toThrow();
expect(() => signTxWithKeyPairsAndTransactionBuilder(parsed, keyChain, TEST_NETWORK)).toThrow();
});

@@ -82,3 +82,3 @@

expect(() => signTxWithKeyPairs(parsed, keyChain, TEST_NETWORK)).toThrow();
expect(() => signTxWithKeyPairsAndTransactionBuilder(parsed, keyChain, TEST_NETWORK)).toThrow();
});

@@ -94,3 +94,3 @@

const signedTx = signTxWithKeyPairs(parsed, keyChain, TEST_NETWORK);
const signedTx = signTxWithKeyPairsAndTransactionBuilder(parsed, keyChain, TEST_NETWORK);
const actualPayload = toWDBroadcastPayload(signedTx);

@@ -100,5 +100,111 @@ const expectedPayload = {

signatures: ["304402201fe1ee3f6bffdb01ff756beb42d1973c79bec5f42e35258b78d770751db7c15b02206f96be67d74e0d503afde5ecc56407b3b09bc4de7a03715bc2191cb29add4108"],
pubkeys: ["m/49'/1'/0'/0/0"]
pubkeys: ["03b556e3484fdd0e33bcddbb3775958ff23d28b18190f1e37986f7b62747a7ff2c"]
}
expect(actualPayload).toEqual(expectedPayload);
});
const ANOTHER_TEST_SEED = "proof client label tragic dilemma base exclude dawn eight make economy arch"
const TEST_LEGACY_TX =
"01000000011F482468DA36F8FE6E362EC477CB499040670691DFFDE6AE024BDC327A9521BF000000" +
"001976A914AD5C03E7556D9ADBF0F97F750D9744070DB384E288ACFFFFFFFF0200E1F50500000000" +
"1976A914A8D8164837AC86AD2A2DC6C0A288D60153F790A188AC2C081024010000001976A914897A" +
"D9E309DE8B24E20A60017F6A3731C5149F5788AC65000000"
const TEST_SEGWIT_TX =
"0100000000010188B8D6D2E2106432D9E1D1525F339D1CF3C09E0CF7C0A722428EF2D87908D48900" +
"000000160014C8771AAC8641DA24220E8B5A2AC1FA56C9AA0FC7FFFFFFFF0200E1F5050000000019" +
"76A914A08BB1CDBFDD3EBD4FEF98359984CF294CE6233E88AC4C0B102401000000160014A072A72A" +
"C84FD4924781A5084918C42437EB92C80100A4000000"
const TEST_LEGACY_TX_CACHE = [
"020000000001010000000000000000000000000000000000000000000000000000000000000000FF" +
"FFFFFF03510101FFFFFFFF0200F2052A010000001976A914AD5C03E7556D9ADBF0F97F750D974407" +
"0DB384E288AC0000000000000000266A24AA21A9EDE2F61C3F71D1DEFD3FA999DFA36953755C6906" +
"89799962B48BEBD836974E8CF9012000000000000000000000000000000000000000000000000000" +
"0000000000000000000000"
]
const TEST_SEGWIT_TX_CACHE = [
"020000000001010000000000000000000000000000000000000000000000000000000000000000FF" +
"FFFFFF0401680101FFFFFFFF0200F2052A01000000160014C8771AAC8641DA24220E8B5A2AC1FA56" +
"C9AA0FC70000000000000000266A24AA21A9EDE2F61C3F71D1DEFD3FA999DFA36953755C69068979" +
"9962B48BEBD836974E8CF90120000000000000000000000000000000000000000000000000000000" +
"000000000000000000"
]
function testSignTxFromSeed(tx: string, mnemonic_seed: string, cache: string[], derivations: string[], expected_len_diff: number) : SignedTransaction {
const seed = seedFromMnemonic(mnemonic_seed, bitcoin.networks.regtest);
const parsed = Transaction.fromHex(tx);
const keyChain = generateKeychain(seed, derivations, bitcoin.networks.regtest);
const signedTx = signTxWithKeyPairs(parsed, cache, keyChain);
expect(signedTx.signed_tx.toHex().length - parsed.toHex().length).toBe(expected_len_diff);
return signedTx
}
test("signTxWithKeyPairs signs legacy transaction from seed with derivation", () => {
testSignTxFromSeed(TEST_LEGACY_TX, ANOTHER_TEST_SEED, TEST_LEGACY_TX_CACHE, ["m/44'/1'/0'/0/0"], 164);
});
test("signTxWithKeyPairs signs segwit transaction from seed with derivation", () => {
testSignTxFromSeed(TEST_SEGWIT_TX, ANOTHER_TEST_SEED, TEST_SEGWIT_TX_CACHE, ["m/84'/1'/0'/0/0"], 166);
});
function testSignTxFromSeedWithDerivationList(tx: string, mnemonic_seed: string, cache: string[], derivations: string[], expected_path: string, expected_len_diff: number) : void {
const seed = seedFromMnemonic(mnemonic_seed, bitcoin.networks.regtest);
const parsed = Transaction.fromHex(tx);
const keyChain = generateKeychain(seed, derivations, bitcoin.networks.regtest);
const signedTx = signTxWithKeyPairs(parsed, cache, keyChain);
expect(signedTx.signed_tx.toHex().length - parsed.toHex().length).toBe(expected_len_diff);
expect(signedTx.pubkey_paths.length).toBe(1);
expect(signedTx.pubkey_paths[0]).toBe(expected_path);
}
test("signTxWithKeyPairs signs legacy regtest transaction from seed with derivation in list", () => {
testSignTxFromSeedWithDerivationList(TEST_LEGACY_TX, ANOTHER_TEST_SEED, TEST_LEGACY_TX_CACHE, ["m/44'/1'/0'/0/0", "m/44'/1'/0'/1/1"], "m/44'/1'/0'/0/0", 164);
});
test("signTxWithKeyPairs signs segwit regtest transaction from seed with derivation in list", () => {
testSignTxFromSeedWithDerivationList(TEST_SEGWIT_TX, ANOTHER_TEST_SEED, TEST_SEGWIT_TX_CACHE, ["m/84'/1'/0'/0/0", "m/84'/1'/0'/1/1"], "m/84'/1'/0'/0/0", 166);
});
test("signTxWithKeyPairs signs legacy regtest transaction from seed with derivation not first in list", () => {
testSignTxFromSeedWithDerivationList(TEST_LEGACY_TX, ANOTHER_TEST_SEED, TEST_LEGACY_TX_CACHE, ["m/44/1/0/0/0", "m/44'/1'/0'/0/0", "m/44'/1'/1'/0/0"], "m/44'/1'/0'/0/0", 164);
});
test("signTxWithKeyPairs signs segwit regtest transaction from seed with derivation not first in list", () => {
testSignTxFromSeedWithDerivationList(TEST_SEGWIT_TX, ANOTHER_TEST_SEED, TEST_SEGWIT_TX_CACHE, ["m/84/1/0/0/0", "m/84'/1'/0'/0/0", "m/84'/1'/1'/0/0"], "m/84'/1'/0'/0/0", 166);
});
test("signTxWithKeyPairs fails to sign legacy regtest transaction from seed with wrong derivation", () => {
expect(() => testSignTxFromSeed(TEST_LEGACY_TX, ANOTHER_TEST_SEED, TEST_LEGACY_TX_CACHE, ["m/44'/1'/0'/0/1"], 164)).toThrow();
});
test("signTxWithKeyPairs fails to sign segwit regtest transaction from seed with wrong derivation", () => {
expect(() => testSignTxFromSeed(TEST_SEGWIT_TX, ANOTHER_TEST_SEED, TEST_SEGWIT_TX_CACHE, ["m/84'/1'/0'/0/1"], 166)).toThrow();
});
test("signTxWithKeyPairs hardened derivation matters with legacy tx", () => {
expect(() => testSignTxFromSeed(TEST_LEGACY_TX, ANOTHER_TEST_SEED, TEST_LEGACY_TX_CACHE, ["m/44/1/0/0/1"], 164)).toThrow();
});
test("signTxWithKeyPairs hardened derivation matters with segwit tx", () => {
expect(() => testSignTxFromSeed(TEST_SEGWIT_TX, ANOTHER_TEST_SEED, TEST_SEGWIT_TX_CACHE, ["m/84/1/0/0/1"], 166)).toThrow();
});
test("signTxWithKeyPairs: toWDBroadcastPayload transforms a transaction correctly", () => {
const signedTx = testSignTxFromSeed(TEST_LEGACY_TX, ANOTHER_TEST_SEED, TEST_LEGACY_TX_CACHE, ["m/44'/1'/0'/0/0"], 164);
const actualPayload = toWDBroadcastPayload(signedTx);
const expectedPayload = {
raw_transaction: "01000000011f482468da36f8fe6e362ec477cb499040670691dffde6ae024bdc327a9521bf" +
"000000001976a914ad5c03e7556d9adbf0f97f750d9744070db384e288acffffffff0200e1" +
"f505000000001976a914a8d8164837ac86ad2a2dc6c0a288d60153f790a188ac2c08102401" +
"0000001976a914897ad9e309de8b24e20a60017f6a3731c5149f5788ac65000000",
signatures: ["3045022100a0850959a65799bda24f1d490fd8e46f0a017546dfe0c933331fef313478223d0220651b6c0488837133a70ec4099475d9b22dc384535d9bfba82c531c394c3e2836"],
pubkeys: ["029069d4f870f439053e2a3b15a93c263677b62c91f1339a58007f549e0da0b34d"]
}
expect(actualPayload).toEqual(expectedPayload);
});
import type { Arguments, CommandBuilder } from 'yargs';
import { seedFromMnemonic } from '../lib/seeds';
import { signTxWithKeyPairs, generateKeychain, toWDBroadcastPayload } from '../lib/sign';
import { signTxWithKeyPairsAndTransactionBuilder, generateKeychain, toWDBroadcastPayload } from '../lib/sign';
import { Transaction } from 'bitcoinjs-lib';

@@ -45,3 +45,3 @@ import * as fs from 'fs';

network);
const signedTx = signTxWithKeyPairs(parsed, keyChain, network);
const signedTx = signTxWithKeyPairsAndTransactionBuilder(parsed, keyChain, network);
log.info("Signed the transaction", signedTx);

@@ -48,0 +48,0 @@ log.info("WD format", toWDBroadcastPayload(signedTx));

@@ -25,8 +25,10 @@ import { BIP32Interface } from 'bip32';

pubkey_paths: string[];
pubkeys: string[];
}
export function signTxWithKeyPairs(tx: Transaction, keyChain: Keychain, network?: Network): SignedTransaction {
export function signTxWithKeyPairsAndTransactionBuilder(tx: Transaction, keyChain: Keychain, network?: Network): SignedTransaction {
const txb = bitcoin.TransactionBuilder.fromTransaction(tx, network || DEFAULT_NETWORK);
const pubkey_paths = Array<string>();
const pubkeys = Array<string>();

@@ -40,5 +42,7 @@ // This assumes all inputs are spending utxos sent to the same Dogecoin P2PKH address (starts with D)

pubkey_paths.push(keyChain[j].path)
pubkeys.push(keyChain[j].keypair.publicKey.toString('hex'))
break;
} catch (_e) {
if (j == keyChainSize - 1) {
console.error(_e)
if (keyChainSize == 1) {

@@ -57,6 +61,99 @@ throw `The single key in the keychain cannot sign this tx's input ${i}`;

signed_tx,
pubkey_paths
pubkey_paths,
pubkeys
};
}
function addInputsToPsbt(psbt: bitcoin.Psbt, tx: Transaction, prevTxCache: string[]) {
for (let i = 0; i < tx.ins.length; i++) {
const prevTx = Transaction.fromHex(prevTxCache[i]);
const utxoScript = prevTx.outs[tx.ins[i].index].script
const firstByteOfUtxoScript = utxoScript.readUInt8(0);
if (firstByteOfUtxoScript >= 0 && firstByteOfUtxoScript <= 16) {
// We are spending segwit UTXO
psbt.addInput({
hash: tx.ins[i].hash,
index: tx.ins[i].index,
witnessUtxo: {
script: utxoScript,
value: prevTx.outs[tx.ins[i].index].value
}
});
} else {
// We are spending something else
psbt.addInput({
hash: tx.ins[i].hash,
index: tx.ins[i].index,
nonWitnessUtxo: Buffer.from(prevTxCache[i], 'hex')
});
}
}
}
function addOutputsToPsbt(psbt: bitcoin.Psbt, tx: Transaction) {
for (let i = 0; i < tx.outs.length; i++) {
psbt.addOutput({
script: tx.outs[i].script,
value: tx.outs[i].value,
});
}
}
function craftTxWithPsbt(tx: Transaction, prevTxCache: string[]): bitcoin.Psbt {
const psbt = new bitcoin.Psbt();
psbt.setVersion(tx.version);
psbt.setLocktime(tx.locktime)
addInputsToPsbt(psbt, tx, prevTxCache)
addOutputsToPsbt(psbt, tx)
return psbt
}
function signTxWithPsbt(psbt: bitcoin.Psbt, tx: Transaction, keyChain: Keychain): {pubkey_paths: string[], pubkeys: string[]} {
const pubkey_paths: string[] = []
const pubkeys: string[] = []
// This assumes all inputs are spending utxos sent to the same address
for (let i = 0; i < tx.ins.length; i++) {
const keyChainSize = keyChain.length;
for (let j = 0; j < keyChainSize; j++) {
try {
psbt.signInput(i, keyChain[j].keypair);
pubkey_paths.push(keyChain[j].path)
pubkeys.push(keyChain[j].keypair.publicKey.toString('hex'))
break;
} catch (_e) {
if (j == keyChainSize - 1) {
console.error(_e)
if (keyChainSize == 1) {
throw `The single key in the keychain cannot sign this tx's input ${i}`;
}
throw `All ${keyChainSize} key(s) in the keychain have been tested and none can sign this tx's input ${i}`;
}
}
}
}
return { pubkey_paths: pubkey_paths, pubkeys: pubkeys }
}
export function signTxWithKeyPairs(tx: Transaction, prevTxCache: string[], keyChain: Keychain): SignedTransaction {
const psbt = craftTxWithPsbt(tx, prevTxCache)
const { pubkey_paths: pubkey_paths, pubkeys: pubkeys } = signTxWithPsbt(psbt, tx, keyChain)
if (!psbt.validateSignaturesOfAllInputs()) {
console.error("psbt.validateSignaturesOfAllInputs() returned false");
}
psbt.finalizeAllInputs();
const signed_tx = psbt.extractTransaction();
return {
unsigned_raw: tx.toHex(),
signed_tx,
pubkey_paths,
pubkeys
};
}
export function toWDBroadcastPayload(tx: SignedTransaction): {

@@ -68,9 +165,13 @@ raw_transaction: string,

const s_tx = tx.signed_tx;
const signatures = Array<string>();
const pubkeys = tx.pubkey_paths;
for (const input of s_tx.ins) {
const signatures = Array<string>();
const pubkeys = tx.pubkeys;
for (const input of s_tx.ins) {
if (input.script.length > 0) {
const sig_size = input.script.readUInt8(0);
const der_sig = input.script.subarray(1, sig_size).toString('hex');
signatures.push(der_sig);
} else if (input.witness.length > 0) {
signatures.push(input.witness[0].subarray(0, input.witness[0].length-1).toString('hex'));
}
}

@@ -77,0 +178,0 @@ return {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc