diff --git a/modules/utxo-core/package.json b/modules/utxo-core/package.json index 6aedffdfe8..c54660383f 100644 --- a/modules/utxo-core/package.json +++ b/modules/utxo-core/package.json @@ -56,7 +56,8 @@ "@bitgo/utxo-lib": "^11.6.3", "@bitgo/wasm-miniscript": "2.0.0-beta.7", "bip174": "npm:@bitgo-forks/bip174@3.1.0-master.4", - "bitcoinjs-message": "npm:@bitgo-forks/bitcoinjs-message@1.0.0-master.3" + "bitcoinjs-message": "npm:@bitgo-forks/bitcoinjs-message@1.0.0-master.3", + "fast-sha256": "^1.3.0" }, "gitHead": "18e460ddf02de2dbf13c2aa243478188fb539f0c" } diff --git a/modules/utxo-core/src/bip322/index.ts b/modules/utxo-core/src/bip322/index.ts new file mode 100644 index 0000000000..6cd3479af0 --- /dev/null +++ b/modules/utxo-core/src/bip322/index.ts @@ -0,0 +1 @@ +export * from './toSpend'; diff --git a/modules/utxo-core/src/bip322/toSpend.ts b/modules/utxo-core/src/bip322/toSpend.ts new file mode 100644 index 0000000000..d0aab1af77 --- /dev/null +++ b/modules/utxo-core/src/bip322/toSpend.ts @@ -0,0 +1,24 @@ +import { Hash } from 'fast-sha256'; + +export const BIP322_TAG = 'BIP0322-signed-message'; + +/** + * Perform a tagged hash + * + * @param {string | Buffer} message - The message to hash as a Buffer or utf-8 string + * @param {Buffer} [tag=BIP322_TAG] - The tag to use for hashing, defaults to BIP322_TAG. + * @returns {Buffer} - The resulting hash of the message with the tag. + */ +export function hashMessageWithTag(message: string | Buffer, tag = BIP322_TAG): Buffer { + // Compute the message hash - SHA256(SHA256(tag) || SHA256(tag) || message) + // Reference: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#full + const tagHasher = new Hash(); + tagHasher.update(Buffer.from(BIP322_TAG)); + const tagHash = tagHasher.digest(); + const messageHasher = new Hash(); + messageHasher.update(tagHash); + messageHasher.update(tagHash); + messageHasher.update(Buffer.from(message)); + const messageHash = messageHasher.digest(); + return Buffer.from(messageHash); +} diff --git a/modules/utxo-core/src/index.ts b/modules/utxo-core/src/index.ts index 31db24d010..e499eb472b 100644 --- a/modules/utxo-core/src/index.ts +++ b/modules/utxo-core/src/index.ts @@ -3,6 +3,7 @@ export * as descriptor from './descriptor'; export * as testutil from './testutil'; export * as paygo from './paygo'; export * as bip32utils from './bip32utils'; +export * as bip322 from './bip322'; export * from './dustThreshold'; export * from './Output'; export * from './xOnlyPubkey'; diff --git a/modules/utxo-core/test/bip322/toSpend.ts b/modules/utxo-core/test/bip322/toSpend.ts new file mode 100644 index 0000000000..d279d83687 --- /dev/null +++ b/modules/utxo-core/test/bip322/toSpend.ts @@ -0,0 +1,30 @@ +import assert from 'assert'; + +import { hashMessageWithTag } from '../../src/bip322'; + +describe('to_spend', function () { + describe('Message hashing', function () { + // Test vectors from BIP322 + // Source: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#message-hashing + const fixtures = [ + { + message: '', + hash: 'c90c269c4f8fcbe6880f72a721ddfbf1914268a794cbb21cfafee13770ae19f1', + }, + { + message: 'Hello World', + hash: 'f0eb03b1a75ac6d9847f55c624a99169b5dccba2a31f5b23bea77ba270de0a7a', + }, + ]; + fixtures.forEach(({ message, hash }) => { + it(`should hash the message "${message}"`, function () { + const result = hashMessageWithTag(Buffer.from(message)); + assert.deepStrictEqual( + result.toString('hex'), + hash, + `Hash for message "${message}" does not match expected value` + ); + }); + }); + }); +});