PHP WebShell
Текущая директория: /opt/BitGoJS/modules/utxo-lib/test/bitgo/psbt
Просмотр файла: Musig2.ts
import * as assert from 'assert';
import {
createPsbtFromHex,
getExternalChainCode,
getInternalChainCode,
isSegwit,
parsePsbtInput,
ProprietaryKeySubtype,
PSBT_PROPRIETARY_IDENTIFIER,
RootWalletKeys,
scriptTypeForChain,
UtxoTransaction,
verifySignatureWithUnspent,
} from '../../../src/bitgo';
import { getKeyTriple, verifyFullySignedSignatures } from '../../../src/testutil';
import {
createTapInternalKey,
createTapOutputKey,
createTapTweak,
decodePsbtMusig2Nonce,
decodePsbtMusig2Participants,
encodePsbtMusig2PartialSig,
musig2PartialSign,
assertPsbtMusig2Nonces,
Musig2NonceStore,
} from '../../../src/bitgo/Musig2';
import { scriptTypes2Of3, toXOnlyPublicKey } from '../../../src/bitgo/outputScripts';
import {
constructPsbt,
getUnspents,
invalidPartialSig,
invalidParticipantPubKeys,
invalidTapInputKey,
invalidTapOutputKey,
invalidTxHash,
dummyAggNonce,
validateNoncesKeyVals,
validatePartialSigKeyVals,
validateParticipantsKeyVals,
validatePsbtP2trMusig2Input,
validatePsbtP2trMusig2Output,
dummyParticipantPubKeys,
dummyPrivateKey,
dummyPubNonce,
dummyTapInternalKey,
dummyTapOutputKey,
dummyPartialSig,
validateFinalizedInput,
network,
validateParsedTaprootKeyPathTxInput,
validateParsedTaprootScriptPathTxInput,
validateParsedTaprootKeyPathPsbt,
validateParsedTaprootScriptPathPsbt,
rootWalletKeys,
} from './Musig2Util';
const p2trMusig2Unspent = getUnspents(['p2trMusig2'], rootWalletKeys);
const outputType = 'p2trMusig2';
const CHANGE_INDEX = 100;
describe('p2trMusig2', function () {
describe('p2trMusig2 key path', function () {
it(`create psbt, nonces, sign (internal verify) - success`, function () {
const walletKeys = rootWalletKeys.deriveForChainAndIndex(getExternalChainCode('p2trMusig2'), 0);
const unspents = getUnspents(
scriptTypes2Of3.map((t) => t),
rootWalletKeys
);
// WP creates PSBT during build API, serializes it, and sends the psbt to user
const buildPsbt = constructPsbt(unspents, rootWalletKeys, 'bitgo', 'user', outputType);
const buildPsbtSer = buildPsbt.toHex();
// User de-serialises the psbt, ands the user nonce, and sends it to the hsm so that it can add the bitgo nonce
const userPsbt = createPsbtFromHex(buildPsbtSer, network);
userPsbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
const userPsbtSer = userPsbt.toHex();
// HSM deserializes the user psbt, adds the deterministic bitgo nonce, and sends that back to the user
const bitgoPsbt = createPsbtFromHex(userPsbtSer, network);
bitgoPsbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo, { deterministic: true });
const bitgoPsbtSer = bitgoPsbt.toHex();
// User combines the psbt with the bitgo nonce, adds user signature, and sends half-signed to hsm
const bitgoPsbtDeser = createPsbtFromHex(bitgoPsbtSer, network);
userPsbt.combine(bitgoPsbtDeser);
userPsbt.signAllInputsHD(rootWalletKeys.user);
const userPsbtHalfSignedHex = userPsbt.toHex();
// WP de-serialises the psbt and validates user sig
const userPsbtDes = createPsbtFromHex(userPsbtHalfSignedHex, network);
assert.ok(userPsbtDes.validateTaprootMusig2SignaturesOfInput(4, walletKeys.user.publicKey));
// WP sends to hsm for signature and returns a fully signed psbt
const psbt = createPsbtFromHex(userPsbtHalfSignedHex, network);
psbt.signAllInputsHD(rootWalletKeys.bitgo, { deterministic: true });
unspents.forEach((unspent, index) => {
if (scriptTypeForChain(unspent.chain) !== 'p2trMusig2') {
assert.strictEqual(psbt.getProprietaryKeyVals(index).length, 0);
return;
}
validatePsbtP2trMusig2Input(psbt, index, unspent, 'keyPath');
validatePsbtP2trMusig2Output(psbt, 0);
validateParticipantsKeyVals(psbt, index, unspent);
validateNoncesKeyVals(psbt, index, unspent);
validatePartialSigKeyVals(psbt, index, unspent);
});
assert.ok(psbt.validateSignaturesOfAllInputs());
psbt.finalizeAllInputs();
unspents.forEach((unspent, index) => {
validateFinalizedInput(psbt, index, unspent);
});
const tx = psbt.extractTransaction() as UtxoTransaction<bigint>;
assert.ok(verifyFullySignedSignatures(tx, unspents, rootWalletKeys, 'bitgo', 'user'));
unspents.map((unspent, inputIndex) => {
assert.deepStrictEqual(verifySignatureWithUnspent(tx, inputIndex, unspents, rootWalletKeys), [
true,
false,
true,
]);
});
});
it(`parse tx`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'bitgo', 'user', outputType);
validateParsedTaprootKeyPathPsbt(psbt, 0, 'unsigned');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
psbt.signAllInputsHD(rootWalletKeys.user);
validateParsedTaprootKeyPathPsbt(psbt, 0, 'halfsigned');
psbt.signAllInputsHD(rootWalletKeys.bitgo);
validateParsedTaprootKeyPathPsbt(psbt, 0, 'fullysigned');
psbt.finalizeAllInputs();
assert.throws(
() => parsePsbtInput(psbt.data.inputs[0]),
(e: any) => e.message === 'Finalized PSBT parsing is not supported'
);
const tx = psbt.extractTransaction() as UtxoTransaction<bigint>;
validateParsedTaprootKeyPathTxInput(psbt, tx);
});
describe('create nonce', function () {
it(`update with new nonce should be allowed`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
let noncesKeyVals = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
});
assert.strictEqual(noncesKeyVals.length, 1);
const userNonceKey = noncesKeyVals[0].key.keydata;
const userNonceValue = noncesKeyVals[0].value;
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
noncesKeyVals = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
});
assert.strictEqual(noncesKeyVals.length, 2);
noncesKeyVals = noncesKeyVals.filter((kv) => kv.key.keydata.equals(userNonceKey));
assert.strictEqual(noncesKeyVals.length, 1);
assert.ok(!noncesKeyVals[0].value.equals(userNonceValue));
});
it(`Cosigner nonce creation fail should not enforce the signer to recreate nonce`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
const tapBip32Derivation = psbt.data.inputs[0].tapBip32Derivation;
psbt.data.inputs[0].tapBip32Derivation = undefined;
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo),
(e: any) => e.message === 'tapBip32Derivation is required to create nonce'
);
psbt.data.inputs[0].tapBip32Derivation = tapBip32Derivation;
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
psbt.signAllInputsHD(rootWalletKeys.user);
psbt.signAllInputsHD(rootWalletKeys.bitgo);
const noncesKeyVals = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
});
assert.strictEqual(noncesKeyVals.length, 2);
const partialSigKeyVals = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PARTIAL_SIG,
});
assert.strictEqual(partialSigKeyVals.length, 2);
const participantKeyVals = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PARTICIPANT_PUB_KEYS,
});
assert.strictEqual(participantKeyVals.length, 1);
});
it('Cosigner can create a deterministic nonce', function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo, { deterministic: true });
const noncesKeyVals = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
});
assert.strictEqual(noncesKeyVals.length, 2);
});
it('Cosigner cannot create a deterministic nonce if there is no signer nonce', function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo, { deterministic: true }),
(e: any) => e.message === 'No nonces found on input #0'
);
let noncesKeyVals = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
});
assert.strictEqual(noncesKeyVals.length, 0);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo, { deterministic: true }),
(e: any) => e.message === 'signer nonce must be set if cosigner nonce is to be derived deterministically'
);
noncesKeyVals = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
});
assert.strictEqual(noncesKeyVals.length, 1);
});
it('Cosigner cannot add entropy to deterministic nonce creation', function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
assert.throws(
() =>
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo, {
deterministic: true,
sessionId: Buffer.allocUnsafe(32),
}),
(e: any) => e.message === 'Cannot add extra entropy when generating a deterministic nonce'
);
const noncesKeyVals = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
});
assert.strictEqual(noncesKeyVals.length, 1);
});
it('Signer cannot create a deterministic nonce', function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user, { deterministic: true }),
(e: any) => e.message === `Only the cosigner's nonce can be set deterministically`
);
const noncesKeyVals = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
});
assert.strictEqual(noncesKeyVals.length, 0);
});
it(`skipped if tapInternalKey doesn't match participant pub keys agg`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.data.inputs[0].tapInternalKey = dummyTapInternalKey;
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user),
(e: any) => e.message === 'tapInternalKey and aggregated participant pub keys does not match'
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 1);
});
it(`fails if sessionId size is invalid`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user, { sessionId: Buffer.allocUnsafe(33) }),
(e: any) => e.message === 'Invalid sessionId size 33'
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 1);
});
it(`fails if private key is missing`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user.neutered()),
(e: any) => e.message === 'private key is required to generate nonce'
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 1);
});
it(`fails if tapBip32Derivation is missing`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.data.inputs[0].tapBip32Derivation = [];
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user),
(e: any) => e.message === 'tapBip32Derivation is required to create nonce'
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 1);
});
it(`fails if participant pub keys is missing`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.data.inputs[0].unknownKeyVals = [];
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user),
(e: any) => e.message === 'Found 0 matching participant key value instead of 1'
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 0);
});
it(`fails if participant pub keys keydata size is invalid`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
const keyVals = psbt.getProprietaryKeyVals(0);
keyVals[0].key.keydata = Buffer.concat([keyVals[0].key.keydata, Buffer.from('dummy')]);
psbt.data.inputs[0].unknownKeyVals = [];
psbt.addProprietaryKeyValToInput(0, keyVals[0]);
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user),
(e: any) => e.message === `Invalid keydata size ${keyVals[0].key.keydata.length} for participant pub keys`
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 1);
});
it(`fails if participant keydata tapOutputKey in invalid`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
const keyVals = psbt.getProprietaryKeyVals(0);
keyVals[0].key.keydata = Buffer.concat([dummyTapOutputKey, keyVals[0].key.keydata.subarray(32)]);
psbt.data.inputs[0].unknownKeyVals = [];
psbt.addProprietaryKeyValToInput(0, keyVals[0]);
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user),
(e: any) => e.message === `Invalid participants keydata tapOutputKey`
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 1);
});
it(`fails if participant keydata tapInternalKey in invalid`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
const keyVals = psbt.getProprietaryKeyVals(0);
keyVals[0].key.keydata = Buffer.concat([keyVals[0].key.keydata.subarray(0, 32), dummyTapInternalKey]);
psbt.data.inputs[0].unknownKeyVals = [];
psbt.addProprietaryKeyValToInput(0, keyVals[0]);
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user),
(e: any) => e.message === `Invalid participants keydata tapInternalKey`
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 1);
});
it(`fails if tapInternalKey and aggregated participant pub keys don't match`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
const keyVals = psbt.getProprietaryKeyVals(0);
const walletKeys = rootWalletKeys.deriveForChainAndIndex(getInternalChainCode('p2trMusig2'), 1);
const tapInternalKey = createTapInternalKey([walletKeys.user.publicKey, walletKeys.bitgo.publicKey]);
const tapOutputKey = createTapOutputKey(tapInternalKey, psbt.data.inputs[0].tapMerkleRoot!);
keyVals[0].key.keydata = Buffer.concat([tapOutputKey, tapInternalKey]);
keyVals[0].value = Buffer.concat([walletKeys.user.publicKey, walletKeys.bitgo.publicKey]);
psbt.data.inputs[0].unknownKeyVals = [];
psbt.addProprietaryKeyValToInput(0, keyVals[0]);
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user),
(e: any) => e.message === `tapInternalKey and aggregated participant pub keys does not match`
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 1);
});
it(`fails if keydata size of participant pub keys is invalid`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
const keyVals = psbt.getProprietaryKeyVals(0);
keyVals[0].key.keydata = Buffer.allocUnsafe(65);
psbt.data.inputs[0].unknownKeyVals = [];
psbt.addProprietaryKeyValToInput(0, keyVals[0]);
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user),
(e: any) => e.message === `Invalid keydata size 65 for participant pub keys`
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 1);
});
it(`fails if valuedata size of participant pub keys is invalid`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
const keyVals = psbt.getProprietaryKeyVals(0);
keyVals[0].value = Buffer.allocUnsafe(67);
psbt.data.inputs[0].unknownKeyVals = [];
psbt.addProprietaryKeyValToInput(0, keyVals[0]);
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user),
(e: any) => e.message === `Invalid valuedata size 67 for participant pub keys`
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 1);
});
it(`fails if duplicate participant pub keys found`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
const keyVals = psbt.getProprietaryKeyVals(0);
keyVals[0].value = Buffer.concat([keyVals[0].value.subarray(33), keyVals[0].value.subarray(33)]);
psbt.data.inputs[0].unknownKeyVals = [];
psbt.addProprietaryKeyValToInput(0, keyVals[0]);
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user),
(e: any) => e.message === `Duplicate participant pub keys found`
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 1);
});
it(`fails if no fingerprint match`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.data.inputs[0].tapBip32Derivation?.forEach((bv) => (bv.masterFingerprint = Buffer.allocUnsafe(4)));
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user),
(e: any) => e.message === 'No bip32Derivation masterFingerprint matched the HD keyPair fingerprint'
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 1);
});
it(`fails if pubkey did not match tapBip32Derivation`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
const walletKeys = rootWalletKeys.deriveForChainAndIndex(getInternalChainCode('p2trMusig2'), CHANGE_INDEX);
psbt.data.inputs[0].tapBip32Derivation?.forEach((bv) => {
bv.path = walletKeys.paths[2];
});
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user),
(e: any) => e.message === 'pubkey did not match bip32Derivation'
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 1);
});
it(`fails if root wallet key derive more than one tapBip32Derivation`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
const walletKeys = rootWalletKeys.deriveForChainAndIndex(
p2trMusig2Unspent[0].chain,
p2trMusig2Unspent[0].index
);
psbt.data.inputs[0].tapBip32Derivation?.forEach((bv, index) => {
bv.path = walletKeys.paths[0];
bv.pubkey = toXOnlyPublicKey(walletKeys.publicKeys[0]);
bv.masterFingerprint = rootWalletKeys.user.fingerprint;
});
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user),
(e: any) => e.message.startsWith('more than one matching derivation for fingerprint')
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 1);
});
it(`fails if derived wallet key does not match any participant key`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
const keyVals = psbt.getProprietaryKeyVals(0);
const walletKeys = rootWalletKeys.deriveForChainAndIndex(getInternalChainCode('p2trMusig2'), 1);
const tapInternalKey = createTapInternalKey([walletKeys.user.publicKey, walletKeys.bitgo.publicKey]);
psbt.data.inputs[0].tapInternalKey = tapInternalKey;
keyVals[0].value = Buffer.concat([walletKeys.user.publicKey, walletKeys.bitgo.publicKey]);
const tapOutputKey = createTapOutputKey(tapInternalKey, psbt.data.inputs[0].tapMerkleRoot!);
keyVals[0].key.keydata = Buffer.concat([tapOutputKey, tapInternalKey]);
psbt.data.inputs[0].unknownKeyVals = [];
psbt.addProprietaryKeyValToInput(0, keyVals[0]);
assert.throws(
() => psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user),
(e: any) => e.message === `participant plain pub key should match one bip32Derivation plain pub key`
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 1);
});
});
describe('sign', function () {
it(`fails if privateKey is missing`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
assert.throws(
() => psbt.signTaprootInputHD(0, rootWalletKeys.user.neutered()),
(e: any) => e.message === 'privateKey is required to sign p2tr musig2'
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 3);
});
it(`fails if tapInternalKey is missing`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
const walletKeys = rootWalletKeys.deriveForChainAndIndex(
p2trMusig2Unspent[0].chain,
p2trMusig2Unspent[0].index
);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
psbt.data.inputs[0].tapInternalKey = undefined;
assert.throws(
() =>
psbt.signTaprootMusig2Input(0, {
publicKey: walletKeys.user.publicKey,
privateKey: walletKeys.user.privateKey!,
}),
(e: any) => e.message === 'not a taproot musig2 input'
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 3);
});
it('only the cosigner can add a deterministic signature', function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo, { deterministic: true });
assert.throws(
() => psbt.signInputHD(0, rootWalletKeys.user, { deterministic: true }),
(e: any) => e.message === 'can only add a deterministic signature on the cosigner'
);
});
it('cosigner can sign deterministically', function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo, { deterministic: true });
psbt.signAllInputsHD(rootWalletKeys.user);
psbt.signAllInputsHD(rootWalletKeys.bitgo, { deterministic: true });
assert.ok(psbt.validateSignaturesOfAllInputs());
assert.ok(psbt.finalizeAllInputs());
});
it('cosigner can sign non-deterministically', function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo, { deterministic: false });
psbt.signAllInputsHD(rootWalletKeys.user);
psbt.signAllInputsHD(rootWalletKeys.bitgo, { deterministic: false });
assert.ok(psbt.validateSignaturesOfAllInputs());
assert.ok(psbt.finalizeAllInputs());
});
});
it(`fails if tapMerkleRoot is missing`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
const walletKeys = rootWalletKeys.deriveForChainAndIndex(p2trMusig2Unspent[0].chain, p2trMusig2Unspent[0].index);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
psbt.data.inputs[0].tapMerkleRoot = undefined;
assert.throws(
() =>
psbt.signTaprootMusig2Input(0, {
publicKey: walletKeys.user.publicKey,
privateKey: walletKeys.user.privateKey!,
}),
(e: any) => e.message === 'not a taproot musig2 input'
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 3);
});
it(`fails if participant pub keys is missing`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
psbt.data.inputs[0].unknownKeyVals = [];
assert.throws(
() => psbt.signTaprootInputHD(0, rootWalletKeys.user),
(e: any) => e.message === 'Found 0 matching participant key value instead of 1'
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 0);
});
it(`fails if signer pub key is not matching any participant pub keys`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
assert.throws(
() =>
psbt.signTaprootMusig2Input(0, {
privateKey: rootWalletKeys.backup.privateKey!,
publicKey: rootWalletKeys.backup.publicKey!,
}),
(e: any) => e.message === 'signer pub key should match one of participant pub keys'
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 3);
});
it(`fails if more than 2 nonce key value exists`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
psbt.data.inputs[0].unknownKeyVals?.push(psbt.data.inputs[0].unknownKeyVals[2]);
assert.throws(
() => psbt.signTaprootInputHD(0, rootWalletKeys.user),
(e: any) => e.message === 'Found 3 matching nonce key value instead of 1 or 2'
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 4);
});
it(`fails if 2 nonce key value do not exist`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
psbt.data.inputs[0].unknownKeyVals?.splice(2);
assert.throws(
() => psbt.signTaprootInputHD(0, rootWalletKeys.user),
(e: any) => e.message === 'Found 1 matching nonce key value instead of 2'
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 2);
});
it(`fails if nonce keydata size is invalid`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
const keyVals = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
});
keyVals[1].key.keydata = Buffer.concat([keyVals[1].key.keydata, Buffer.from('dummy')]);
psbt.data.inputs[0].unknownKeyVals?.splice(2);
psbt.addProprietaryKeyValToInput(0, keyVals[1]);
assert.throws(
() => psbt.signTaprootInputHD(0, rootWalletKeys.user),
(e: any) => e.message === `Invalid keydata size ${keyVals[1].key.keydata.length} for nonce`
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 3);
});
it(`fails if nonce valuedata size is invalid`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
const keyVals = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
});
keyVals[1].value = Buffer.concat([keyVals[1].value, Buffer.from('dummy')]);
psbt.data.inputs[0].unknownKeyVals?.splice(2);
psbt.addProprietaryKeyValToInput(0, keyVals[1]);
assert.throws(
() => psbt.signTaprootInputHD(0, rootWalletKeys.user),
(e: any) => e.message === `Invalid valuedata size ${keyVals[1].value.length} for nonce`
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 3);
});
it(`fails if nonce keydata is invalid`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
const dummyRootWalletKeys = new RootWalletKeys(getKeyTriple('dummy'));
const dummyP2trMusig2Unspent = getUnspents(['p2trMusig2'], dummyRootWalletKeys);
const dummyPsbt = constructPsbt(dummyP2trMusig2Unspent, dummyRootWalletKeys, 'user', 'bitgo', 'p2sh');
dummyPsbt.setAllInputsMusig2NonceHD(dummyRootWalletKeys.user);
dummyPsbt.setAllInputsMusig2NonceHD(dummyRootWalletKeys.bitgo);
const dummyKeyVals = dummyPsbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
});
psbt.data.inputs[0].unknownKeyVals?.splice(1);
dummyKeyVals.forEach((kv, i) => psbt.addProprietaryKeyValToInput(0, dummyKeyVals[i]));
assert.throws(
() => psbt.signTaprootInputHD(0, rootWalletKeys.user),
(e: any) => e.message === `Invalid nonce keydata participant pub key`
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 3);
});
it(`fails if nonce keydata tapOutputKey is invalid`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', 'p2sh');
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
const keyVals = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
});
keyVals[1].key.keydata = Buffer.concat([keyVals[1].key.keydata.subarray(0, 33), dummyTapOutputKey]);
psbt.data.inputs[0].unknownKeyVals?.splice(2);
psbt.addProprietaryKeyValToInput(0, keyVals[1]);
assert.throws(
() => psbt.signTaprootInputHD(0, rootWalletKeys.user),
(e: any) => e.message === `Invalid nonce keydata tapOutputKey`
);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 3);
});
});
describe('p2trMusig2 script path', function () {
it(`psbt creation success and musig2 skips`, function () {
let psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'backup', outputType);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.backup);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 0);
psbt.signAllInputsHD(rootWalletKeys.user);
psbt.signAllInputsHD(rootWalletKeys.backup);
validatePsbtP2trMusig2Input(psbt, 0, p2trMusig2Unspent[0], 'scriptPath');
validatePsbtP2trMusig2Output(psbt, 0);
assert.ok(psbt.validateSignaturesOfAllInputs());
psbt.finalizeAllInputs();
validateFinalizedInput(psbt, 0, p2trMusig2Unspent[0], 'scriptPath');
let tx = psbt.extractTransaction() as UtxoTransaction<bigint>;
assert.ok(verifyFullySignedSignatures(tx, p2trMusig2Unspent, rootWalletKeys, 'user', 'backup'));
psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'bitgo', 'backup', outputType);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.backup);
psbt.signAllInputsHD(rootWalletKeys.bitgo);
psbt.signAllInputsHD(rootWalletKeys.backup);
assert.strictEqual(psbt.getProprietaryKeyVals(0).length, 0);
validatePsbtP2trMusig2Input(psbt, 0, p2trMusig2Unspent[0], 'scriptPath');
validatePsbtP2trMusig2Output(psbt, 0);
assert.ok(psbt.validateSignaturesOfAllInputs());
psbt.finalizeAllInputs();
validateFinalizedInput(psbt, 0, p2trMusig2Unspent[0], 'scriptPath');
tx = psbt.extractTransaction() as UtxoTransaction<bigint>;
assert.ok(verifyFullySignedSignatures(tx, p2trMusig2Unspent, rootWalletKeys, 'bitgo', 'backup'));
});
it(`parse tx`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'backup', outputType);
validateParsedTaprootScriptPathPsbt(psbt, 0, 'unsigned');
psbt.signAllInputsHD(rootWalletKeys.user);
validateParsedTaprootScriptPathPsbt(psbt, 0, 'halfsigned');
psbt.signAllInputsHD(rootWalletKeys.backup);
validateParsedTaprootScriptPathPsbt(psbt, 0, 'fullysigned');
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction() as UtxoTransaction<bigint>;
const psbtDuplicate = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'backup', outputType);
validateParsedTaprootScriptPathTxInput(psbtDuplicate, tx, 0);
});
});
describe('validate p2tr Musig2 signatures', function () {
it(`validate with pubkey`, function () {
const walletKeys = rootWalletKeys.deriveForChainAndIndex(p2trMusig2Unspent[0].chain, p2trMusig2Unspent[0].index);
let psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', outputType);
psbt.setAllInputsMusig2Nonce(walletKeys.user);
psbt.setAllInputsMusig2Nonce(walletKeys.bitgo);
psbt.signAllInputsHD(rootWalletKeys.user);
assert.ok(psbt.validateTaprootMusig2SignaturesOfInput(0, walletKeys.user.publicKey));
psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', outputType);
psbt.setInputMusig2Nonce(0, walletKeys.user);
psbt.setInputMusig2NonceHD(0, rootWalletKeys.bitgo);
psbt.signAllInputsHD(rootWalletKeys.user);
assert.ok(psbt.validateTaprootMusig2SignaturesOfInput(0, walletKeys.user.publicKey));
psbt.signAllInputsHD(rootWalletKeys.bitgo);
assert.ok(psbt.validateTaprootMusig2SignaturesOfInput(0, walletKeys.bitgo.publicKey));
});
it(`fails if no sig`, function () {
const walletKeys = rootWalletKeys.deriveForChainAndIndex(p2trMusig2Unspent[0].chain, p2trMusig2Unspent[0].index);
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', outputType);
assert.throws(
() => psbt.validateSignaturesOfAllInputs(),
(e: any) => e.message === `No signatures to validate`
);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
psbt.signAllInputsHD(rootWalletKeys.user);
assert.throws(
() => psbt.validateTaprootMusig2SignaturesOfInput(0, walletKeys.bitgo.publicKey),
(e: any) => e.message === `No signatures for this pubkey`
);
});
it(`fails if no tapInternalKey and tapMerkleRoot`, function () {
const walletKeys = rootWalletKeys.deriveForChainAndIndex(p2trMusig2Unspent[0].chain, p2trMusig2Unspent[0].index);
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', outputType);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
psbt.signAllInputsHD(rootWalletKeys.user);
psbt.signAllInputsHD(rootWalletKeys.bitgo);
const tapInternalKey = psbt.data.inputs[0].tapInternalKey;
psbt.data.inputs[0].tapInternalKey = undefined;
assert.throws(
() => psbt.validateTaprootMusig2SignaturesOfInput(0),
(e: any) => e.message === `both tapInternalKey and tapMerkleRoot are required`
);
psbt.data.inputs[0].tapInternalKey = tapInternalKey;
psbt.data.inputs[0].tapMerkleRoot = undefined;
assert.throws(
() => psbt.validateTaprootMusig2SignaturesOfInput(0, walletKeys.bitgo.publicKey),
(e: any) => e.message === `both tapInternalKey and tapMerkleRoot are required`
);
});
it(`fails if no nonce and sig pub key match`, function () {
let psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', outputType);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
psbt.signAllInputsHD(rootWalletKeys.user);
psbt.signAllInputsHD(rootWalletKeys.bitgo);
const partialSigs = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PARTIAL_SIG,
});
const myRootWalletKeys = new RootWalletKeys(getKeyTriple('dummy'));
const myUnspents = getUnspents(['p2trMusig2'], myRootWalletKeys);
psbt = constructPsbt(myUnspents, myRootWalletKeys, 'user', 'bitgo', outputType);
psbt.setAllInputsMusig2NonceHD(myRootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(myRootWalletKeys.bitgo);
psbt.signAllInputsHD(myRootWalletKeys.user);
psbt.signAllInputsHD(myRootWalletKeys.bitgo);
const participants = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PARTICIPANT_PUB_KEYS,
});
const nonces = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
});
psbt.data.inputs[0].unknownKeyVals = undefined;
psbt.addProprietaryKeyValToInput(0, participants[0]);
psbt.addProprietaryKeyValToInput(0, nonces[0]);
psbt.addProprietaryKeyValToInput(0, nonces[1]);
psbt.addProprietaryKeyValToInput(0, partialSigs[0]);
psbt.addProprietaryKeyValToInput(0, partialSigs[1]);
assert.throws(
() => psbt.validateSignaturesOfAllInputs(),
(e: any) => e.message === `Found no pub nonce for pubkey`
);
});
it(`fails if no valid sig`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', outputType);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
psbt.signAllInputsHD(rootWalletKeys.user);
psbt.signAllInputsHD(rootWalletKeys.bitgo);
const partialSigs = psbt.getProprietaryKeyVals(0, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PARTIAL_SIG,
});
partialSigs[1].value = dummyPartialSig;
psbt.addOrUpdateProprietaryKeyValToInput(0, partialSigs[1]);
assert.ok(!psbt.validateSignaturesOfAllInputs());
});
});
describe('finalizeTaprootMusig2Input', function () {
it('fails if invalid number for sigs', function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', outputType);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
psbt.signAllInputsHD(rootWalletKeys.user);
assert.throws(
() => psbt.finalizeAllInputs(),
(e: any) => e.message === `invalid number of partial signatures 1 to finalize`
);
});
});
describe('Psbt musig2 common functions', function () {
it('output script should match the scriptPubKey in the prevout', function () {
const myRootWalletKeys = new RootWalletKeys(getKeyTriple('dummy'));
const unspents = getUnspents(
scriptTypes2Of3.map((t) => t),
myRootWalletKeys
);
const psbt = constructPsbt(unspents, rootWalletKeys, 'user', 'bitgo', outputType);
unspents.forEach((u, index) => {
const scriptType = scriptTypeForChain(u.chain);
assert.throws(
() =>
scriptType === 'p2trMusig2'
? psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user)
: scriptType === 'p2tr'
? psbt.signTaprootInputHD(index, rootWalletKeys.user)
: psbt.signInputHD(index, rootWalletKeys.user),
(e: any) =>
isSegwit(u.chain) && scriptType !== 'p2shP2wsh'
? e.message === `Witness script for input #${index} doesn't match the scriptPubKey in the prevout`
: e.message === `Redeem script for input #${index} doesn't match the scriptPubKey in the prevout`
);
});
const p2trMusig2ScriptPathPsbt = constructPsbt([unspents[4]], rootWalletKeys, 'user', 'backup', outputType);
assert.throws(
() => p2trMusig2ScriptPathPsbt.signTaprootInputHD(0, rootWalletKeys.user),
(e: any) => e.message === `Witness script for input #0 doesn't match the scriptPubKey in the prevout`
);
});
it(`decodePsbtMusig2ParticipantsKeyValData fails if invalid subtype or identifier is passed`, function () {
const kv = {
key: {
identifier: 'dummy',
subtype: 0x05,
keydata: Buffer.allocUnsafe(1),
},
value: Buffer.allocUnsafe(1),
};
assert.throws(
() => decodePsbtMusig2Participants(kv),
(e: any) =>
e.message === `Invalid identifier ${kv.key.identifier} or subtype ${kv.key.subtype} for participants pub keys`
);
kv.key.identifier = PSBT_PROPRIETARY_IDENTIFIER;
assert.throws(
() => decodePsbtMusig2Participants(kv),
(e: any) =>
e.message === `Invalid identifier ${kv.key.identifier} or subtype ${kv.key.subtype} for participants pub keys`
);
});
it(`decodePsbtMusig2NonceKeyValData fails if invalid subtype or identifier is passed`, function () {
const kv = {
key: {
identifier: 'dummy',
subtype: 0x05,
keydata: Buffer.allocUnsafe(1),
},
value: Buffer.allocUnsafe(1),
};
assert.throws(
() => decodePsbtMusig2Nonce(kv),
(e: any) => e.message === `Invalid identifier ${kv.key.identifier} or subtype ${kv.key.subtype} for nonce`
);
kv.key.identifier = PSBT_PROPRIETARY_IDENTIFIER;
assert.throws(
() => decodePsbtMusig2Nonce(kv),
(e: any) => e.message === `Invalid identifier ${kv.key.identifier} or subtype ${kv.key.subtype} for nonce`
);
});
it(`validatePsbtMusig2NoncesKeyValData fails if participant pub keys are duplicate`, function () {
const nonceKeyValData = [0, 1].map((i) => ({
participantPubKey: dummyParticipantPubKeys[i],
tapOutputKey: dummyTapOutputKey,
pubNonce: dummyPubNonce,
}));
let participantKeyValData = {
participantPubKeys: dummyParticipantPubKeys,
tapInternalKey: dummyTapInternalKey,
tapOutputKey: invalidTapOutputKey,
};
assert.throws(
() => assertPsbtMusig2Nonces(nonceKeyValData, participantKeyValData),
(e: any) => e.message === `invalid size 1. Must use x-only key.`
);
participantKeyValData = {
participantPubKeys: [invalidParticipantPubKeys[0], dummyParticipantPubKeys[0]],
tapInternalKey: dummyTapInternalKey,
tapOutputKey: dummyTapOutputKey,
};
assert.throws(
() => assertPsbtMusig2Nonces(nonceKeyValData, participantKeyValData),
(e: any) => e.message === `invalid size 1. Must use plain key.`
);
participantKeyValData = {
participantPubKeys: [dummyParticipantPubKeys[0], dummyParticipantPubKeys[0]],
tapInternalKey: dummyTapInternalKey,
tapOutputKey: dummyTapOutputKey,
};
assert.throws(
() => assertPsbtMusig2Nonces(nonceKeyValData, participantKeyValData),
(e: any) => e.message === `Duplicate participant pub keys found`
);
});
it(`createTapTweak fails if invalid tapInternalKey or tapMerkleRoot is passed`, function () {
assert.throws(
() => createTapTweak(invalidTapInputKey, dummyTapOutputKey),
(e: any) => e.message === `invalid size 1. Must use x-only key.`
);
assert.throws(
() => createTapTweak(dummyTapInternalKey, invalidTapOutputKey),
(e: any) => e.message === `invalid size 1. Must use tap merkle root.`
);
});
it(`musig2PartialSign fails if invalid txHash is passed`, function () {
assert.throws(
() =>
musig2PartialSign(
dummyPrivateKey,
dummyPubNonce,
{
publicKey: dummyParticipantPubKeys[0],
aggNonce: dummyAggNonce,
msg: invalidTxHash,
},
new Musig2NonceStore()
),
(e: any) => e.message === `invalid size 1. Must use tx hash.`
);
});
it(`encodePsbtMusig2PartialSigKeyKeyValData fails if invalid txHash is passed`, function () {
assert.throws(
() =>
encodePsbtMusig2PartialSig({
partialSig: invalidPartialSig,
participantPubKey: dummyParticipantPubKeys[0],
tapOutputKey: dummyTapOutputKey,
}),
(e: any) => e.message === `Invalid partialSig length 1`
);
});
it(`deleteProprietaryKeyVals`, function () {
const psbt = constructPsbt(p2trMusig2Unspent, rootWalletKeys, 'user', 'bitgo', outputType);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.user);
psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo);
const key = {
identifier: 'DUMMY',
subtype: 100,
keydata: dummyTapOutputKey,
};
psbt.addProprietaryKeyValToInput(0, { key, value: dummyTapInternalKey });
psbt.deleteProprietaryKeyVals(0, { identifier: PSBT_PROPRIETARY_IDENTIFIER });
const keyVal = psbt.getProprietaryKeyVals(0);
assert.strictEqual(keyVal.length, 1);
assert.strictEqual(keyVal[0].key.identifier, 'DUMMY');
});
});
});
Выполнить команду
Для локальной разработки. Не используйте в интернете!