Skip to content

Commit

Permalink
Add sign ecdsa method to bitcoin signer
Browse files Browse the repository at this point in the history
  • Loading branch information
baryon2 committed Jan 2, 2025
1 parent 75c7e11 commit 97278a6
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@leapwallet/leap-keychain",
"version": "0.3.3-beta.3",
"version": "0.3.4-beta.0",
"description": "A javascript library for crypto key management",
"scripts": {
"test:coverage": "nyc mocha",
Expand Down
16 changes: 16 additions & 0 deletions src/key/btc-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { base64, hex } from '@scure/base';
import Container from 'typedi';
import { secp256k1Token } from '../crypto/ecc/secp256k1';
import { P2Ret } from '@scure/btc-signer/payment';
import { signSync } from '@noble/secp256k1';
export type BTCWalletOptions = WalletOptions & { network: typeof NETWORK };

export abstract class BtcWallet {
Expand Down Expand Up @@ -50,6 +51,21 @@ export abstract class BtcWallet {
if (!account.privateKey) throw new Error('Private key not found');
tx.signIdx(account.privateKey, idx);
}

signECDSA(address: string, hash: Uint8Array) {
const accounts = this.getAccountsWithPrivKey();
const account = accounts.find((account) => account.address === address);
if (!account) throw new Error(`No account found for ${address}`);
const [signature, recoveryParam] = signSync(hash, account.privateKey, {
canonical: true,
recovered: true,
der: false,
});
return {
signature,
recoveryParam,
};
}
}

export class BtcWalletHD extends BtcWallet {
Expand Down
9 changes: 9 additions & 0 deletions src/utils/encode-signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,12 @@ export function encodeSecp256k1Pubkey(pubkey: Uint8Array): Pubkey {
value: base64js.fromByteArray(pubkey),
};
}

export function compressSignature(recoveryParam: number, signature: Uint8Array) {
if (!(recoveryParam === 0 || recoveryParam === 1 || recoveryParam === 2 || recoveryParam === 3)) {
throw new Error('recoveryParam must be equal to 0, 1, 2, or 3');
}

let headerByte = recoveryParam + 27 + 4;
return Buffer.concat([Uint8Array.of(headerByte), Uint8Array.from(signature)]).toString('base64');
}
20 changes: 20 additions & 0 deletions test/btc-wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { BtcWalletHD, BtcWalletPk } from '../src/key/btc-wallet';
import expect from 'expect.js';
import { addresses, btcPrivatekey, mnemonic, sbtcPrivatekey } from './mockdata';
import { NETWORK, TEST_NETWORK } from '@scure/btc-signer';
import { base64 } from '@scure/base';
import { compressSignature } from '../src/utils/encode-signature';

beforeEach(() => {
setBip39(Bip39);
Expand Down Expand Up @@ -68,4 +70,22 @@ describe('generate btc wallet', () => {
if (!accounts[0]) throw new Error('No accounts found');
expect(accounts[0].address).to.be(addresses.signet);
});

it('signEcdsa: generates correct signature', () => {
const wallet = BtcWalletHD.generateWalletFromMnemonic(mnemonic, {
addressPrefix: 'bc1q',
paths: ["m/84'/0'/0'/0/0"],
network: NETWORK,
});
const [account] = wallet.getAccounts();
if (!account) throw new Error();

const testHash = 'lZ93LI3uk73n7jGU4os1GIWkEz/4vf//AhBR2m5M/9A=';

const fixture = 'IBU1VH1HFZKtulCFAukOm3JP8QO4ldrqxVohbhY5Qt8YFAxG85AanlP4qPjnOfDlkWGUUTan1gAVad1KcG2FifQ=';

const { signature, recoveryParam } = wallet.signECDSA(account.address, base64.decode(testHash));
const base64Signature = compressSignature(recoveryParam, signature);
expect(base64Signature).to.equal(fixture);
});
});

0 comments on commit 97278a6

Please sign in to comment.