Skip to content

Commit 01a2cd1

Browse files
OttoAllmendingerllm-git
andcommitted
feat(sdk-core): replace utxo-core with secp256k1 for bip32utils
Move bip32utils implementation from utxo-core to secp256k1 module and update import in sdk-core. This allows us to remove the utxo-core dependency from sdk-core. Issue: BTC-0 Co-authored-by: llm-git <llm-git@ttll.de>
1 parent b57480c commit 01a2cd1

File tree

7 files changed

+140
-46
lines changed

7 files changed

+140
-46
lines changed

modules/sdk-core/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
"@bitgo/secp256k1": "^1.8.0",
4646
"@bitgo/sjcl": "^1.0.1",
4747
"@bitgo/statics": "^58.19.0",
48-
"@bitgo/utxo-core": "^1.28.0",
4948
"@bitgo/utxo-lib": "^11.19.0",
5049
"@noble/curves": "1.8.1",
5150
"@stablelib/hex": "^1.0.0",
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { bip32utils } from '@bitgo/utxo-core';
1+
import { bip32utils } from '@bitgo/secp256k1';
22

33
export const signMessage = bip32utils.signMessage;
44
export const verifyMessage = bip32utils.verifyMessage;

modules/secp256k1/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
"@brandonblack/musig": "^0.0.1-alpha.0",
3535
"@noble/secp256k1": "1.6.3",
3636
"bip32": "^3.0.1",
37+
"bitcoinjs-message": "npm:@bitgo-forks/bitcoinjs-message@1.0.0-master.3",
38+
"bs58check": "^2.1.2",
3739
"create-hash": "^1.2.0",
3840
"create-hmac": "^1.1.7",
3941
"ecpair": "npm:@bitgo/ecpair@2.1.0-rc.0"
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { BIP32Interface } from 'bip32';
2+
import * as bitcoinMessage from 'bitcoinjs-message';
3+
4+
const createHash = require('create-hash');
5+
const bs58check = require('bs58check');
6+
7+
/**
8+
* Computes hash160 (RIPEMD160(SHA256(data)))
9+
*/
10+
function hash160(data: Buffer): Buffer {
11+
const sha256Hash = createHash('sha256').update(data).digest();
12+
return createHash('ripemd160').update(sha256Hash).digest();
13+
}
14+
15+
/**
16+
* Encodes a hash with version byte in Base58Check format
17+
*/
18+
function toBase58Check(hash: Buffer, version: number): string {
19+
const payload = Buffer.allocUnsafe(21);
20+
payload.writeUInt8(version, 0);
21+
hash.copy(payload, 1);
22+
return bs58check.encode(payload);
23+
}
24+
25+
// Bitcoin mainnet pubKeyHash version byte
26+
const BITCOIN_PUBKEY_HASH_VERSION = 0x00;
27+
28+
/**
29+
* bip32-aware wrapper around bitcoin-message package
30+
* @see {bitcoinMessage.sign}
31+
*/
32+
export function signMessage(
33+
message: string | Buffer,
34+
privateKey: BIP32Interface | Buffer,
35+
network: { messagePrefix: string }
36+
): Buffer {
37+
if (!Buffer.isBuffer(privateKey)) {
38+
privateKey = privateKey.privateKey as Buffer;
39+
if (!privateKey) {
40+
throw new Error(`must provide privateKey`);
41+
}
42+
}
43+
if (network === null || typeof network !== 'object' || typeof network.messagePrefix !== 'string') {
44+
throw new Error(`invalid argument 'network'`);
45+
}
46+
const compressed = true;
47+
return bitcoinMessage.sign(message, privateKey, compressed, network.messagePrefix);
48+
}
49+
50+
/**
51+
* bip32-aware wrapper around bitcoin-message package
52+
* @see {bitcoinMessage.verify}
53+
*/
54+
export function verifyMessage(
55+
message: string | Buffer,
56+
publicKey: BIP32Interface | Buffer,
57+
signature: Buffer,
58+
network: { messagePrefix: string }
59+
): boolean {
60+
if (!Buffer.isBuffer(publicKey)) {
61+
publicKey = publicKey.publicKey;
62+
}
63+
if (network === null || typeof network !== 'object' || typeof network.messagePrefix !== 'string') {
64+
throw new Error(`invalid argument 'network'`);
65+
}
66+
67+
const address = toBase58Check(hash160(publicKey), BITCOIN_PUBKEY_HASH_VERSION);
68+
return bitcoinMessage.verify(message, address, signature, network.messagePrefix);
69+
}

modules/secp256k1/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ const ECPair: ECPairAPI = ECPairFactory(ecc);
197197
const bip32: BIP32API = BIP32Factory(ecc);
198198
const musig: MuSig = MuSigFactory(crypto);
199199

200+
import * as bip32utils from './bip32utils';
201+
200202
export {
201203
ecc,
202204
ECPair,
@@ -209,4 +211,5 @@ export {
209211
BIP32Interface,
210212
musig,
211213
MuSig,
214+
bip32utils,
212215
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import * as crypto from 'crypto';
2+
import * as assert from 'assert';
3+
4+
import { bip32, bip32utils } from '../src';
5+
6+
const { signMessage, verifyMessage } = bip32utils;
7+
8+
// Bitcoin mainnet message prefix - matches utxolib.networks.bitcoin.messagePrefix
9+
const bitcoinNetwork = {
10+
messagePrefix: '\x18Bitcoin Signed Message:\n',
11+
};
12+
13+
describe('bip32utils', function () {
14+
function getSeedBuffers(length: number) {
15+
return Array.from({ length }).map((_, i) => crypto.createHash('sha256').update(`${i}`).digest());
16+
}
17+
18+
it('signMessage/verifyMessage', function () {
19+
const keys = getSeedBuffers(4).map((seed) => bip32.fromSeed(seed));
20+
const messages = ['hello', 'goodbye', Buffer.from('\x01\x02\x03'), Buffer.from('')];
21+
keys.forEach((key) => {
22+
messages.forEach((message) => {
23+
const signature = signMessage(message, key, bitcoinNetwork);
24+
25+
keys.forEach((otherKey) => {
26+
messages.forEach((otherMessage) => {
27+
const expectValid = message === otherMessage && key === otherKey;
28+
assert.strictEqual(verifyMessage(otherMessage, otherKey, signature, bitcoinNetwork), expectValid);
29+
assert.strictEqual(
30+
verifyMessage(Buffer.from(otherMessage), otherKey, signature, bitcoinNetwork),
31+
expectValid
32+
);
33+
});
34+
});
35+
});
36+
});
37+
});
38+
39+
it('signMessage throws on missing privateKey', function () {
40+
const key = bip32.fromSeed(getSeedBuffers(1)[0]);
41+
const neutered = key.neutered();
42+
assert.throws(() => signMessage('hello', neutered, bitcoinNetwork), /must provide privateKey/);
43+
});
44+
45+
it('signMessage throws on invalid network', function () {
46+
const key = bip32.fromSeed(getSeedBuffers(1)[0]);
47+
assert.throws(() => signMessage('hello', key, null as any), /invalid argument 'network'/);
48+
assert.throws(() => signMessage('hello', key, {} as any), /invalid argument 'network'/);
49+
});
50+
51+
it('verifyMessage throws on invalid network', function () {
52+
const key = bip32.fromSeed(getSeedBuffers(1)[0]);
53+
const signature = signMessage('hello', key, bitcoinNetwork);
54+
assert.throws(() => verifyMessage('hello', key, signature, null as any), /invalid argument 'network'/);
55+
assert.throws(() => verifyMessage('hello', key, signature, {} as any), /invalid argument 'network'/);
56+
});
57+
});
Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,13 @@
1-
import { BIP32Interface } from '@bitgo/secp256k1';
2-
import * as utxolib from '@bitgo/utxo-lib';
3-
import * as bitcoinMessage from 'bitcoinjs-message';
1+
import { bip32utils } from '@bitgo/secp256k1';
2+
43
/**
5-
* bip32-aware wrapper around bitcoin-message package
6-
* @see {bitcoinMessage.sign}
4+
* @deprecated Use signMessage from @bitgo/secp256k1 instead
5+
* @see {bip32utils.signMessage}
76
*/
8-
export function signMessage(
9-
message: string | Buffer,
10-
privateKey: BIP32Interface | Buffer,
11-
network: { messagePrefix: string }
12-
): Buffer {
13-
if (!Buffer.isBuffer(privateKey)) {
14-
privateKey = privateKey.privateKey as Buffer;
15-
if (!privateKey) {
16-
throw new Error(`must provide privateKey`);
17-
}
18-
}
19-
if (network === null || typeof network !== 'object' || typeof network.messagePrefix !== 'string') {
20-
throw new Error(`invalid argument 'network'`);
21-
}
22-
const compressed = true;
23-
return bitcoinMessage.sign(message, privateKey, compressed, network.messagePrefix);
24-
}
7+
export const signMessage = bip32utils.signMessage;
258

269
/**
27-
* bip32-aware wrapper around bitcoin-message package
28-
* @see {bitcoinMessage.verify}
10+
* @deprecated Use verifyMessage from @bitgo/secp256k1 instead
11+
* @see {bip32utils.verifyMessage}
2912
*/
30-
export function verifyMessage(
31-
message: string | Buffer,
32-
publicKey: BIP32Interface | Buffer,
33-
signature: Buffer,
34-
network: { messagePrefix: string }
35-
): boolean {
36-
if (!Buffer.isBuffer(publicKey)) {
37-
publicKey = publicKey.publicKey;
38-
}
39-
if (network === null || typeof network !== 'object' || typeof network.messagePrefix !== 'string') {
40-
throw new Error(`invalid argument 'network'`);
41-
}
42-
43-
const address = utxolib.address.toBase58Check(
44-
utxolib.crypto.hash160(publicKey),
45-
utxolib.networks.bitcoin.pubKeyHash,
46-
utxolib.networks.bitcoin
47-
);
48-
return bitcoinMessage.verify(message, address, signature, network.messagePrefix);
49-
}
13+
export const verifyMessage = bip32utils.verifyMessage;

0 commit comments

Comments
 (0)