PHP WebShell
Текущая директория: /opt/BitGoJS/modules/utxo-lib/src/bitgo
Просмотр файла: Musig2.ts
import { SessionKey } from '@brandonblack/musig';
import {
checkPlainPublicKey,
checkTapMerkleRoot,
checkTxHash,
checkXOnlyPublicKey,
toXOnlyPublicKey,
} from './outputScripts';
import { ecc, musig } from '../noble_ecc';
import { Tuple } from './types';
import { calculateTapTweak, tapTweakPubkey } from '../taproot';
import { Transaction } from '../index';
import { PsbtInput } from 'bip174/src/lib/interfaces';
import {
getPsbtInputProprietaryKeyVals,
ProprietaryKeySubtype,
ProprietaryKeyValue,
PSBT_PROPRIETARY_IDENTIFIER,
} from './PsbtUtil';
/**
* Participant key value object.
*/
export interface PsbtMusig2Participants {
tapOutputKey: Buffer;
tapInternalKey: Buffer;
participantPubKeys: Tuple<Buffer>;
}
export interface PsbtMusig2DeterministicParams {
privateKey: Buffer;
otherNonce: Buffer;
publicKeys: Tuple<Buffer>;
internalPubKey: Buffer;
tapTreeRoot: Buffer;
hash: Buffer;
}
/**
* Nonce key value object.
*/
export interface PsbtMusig2PubNonce {
participantPubKey: Buffer;
tapOutputKey: Buffer;
pubNonce: Buffer;
}
/**
* Partial signature key value object.
*/
export interface PsbtMusig2PartialSig {
participantPubKey: Buffer;
tapOutputKey: Buffer;
partialSig: Buffer;
}
/**
* Because musig uses reference-equal buffers to cache nonces, we wrap it here to allow using
* nonces that are byte-equal but not reference-equal.
*/
export class Musig2NonceStore {
private nonces: Uint8Array[] = [];
/**
* Get original Buffer instance for nonce (which may be a copy).
* @return byte-equal buffer that is reference-equal to what was stored earlier in createMusig2Nonce
*/
getRef(nonce: Uint8Array): Uint8Array {
for (const b of this.nonces) {
if (Buffer.from(b).equals(nonce)) {
return b;
}
}
throw new Error(`unknown nonce`);
}
/**
* Creates musig2 nonce and stores buffer reference.
* tapInternalkey, tapMerkleRoot, tapBip32Derivation for rootWalletKey are required per p2trMusig2 key path input.
* Also participant keys are required from psbt proprietary key values.
* Ref: https://gist.github.com/sanket1729/4b525c6049f4d9e034d27368c49f28a6
* @param privateKey - signer private key
* @param publicKey - signer xy public key
* @param xOnlyPublicKey - tweaked aggregated key (tapOutputKey)
* @param sessionId Additional entropy. If provided it must either be a counter unique to this secret key,
* (converted to an array of 32 bytes), or 32 uniformly random bytes.
*/
createMusig2Nonce(
privateKey: Uint8Array,
publicKey: Uint8Array,
xOnlyPublicKey: Uint8Array,
txHash: Uint8Array,
sessionId?: Buffer
): Uint8Array {
if (txHash.length != 32) {
throw new Error(`Invalid txHash size ${txHash}`);
}
const buf = musig.nonceGen({ secretKey: privateKey, publicKey, xOnlyPublicKey, msg: txHash, sessionId });
this.nonces.push(buf);
return buf;
}
}
/**
* Psbt proprietary key val util function for participants pub keys. SubType is 0x01
* Ref: https://gist.github.com/sanket1729/4b525c6049f4d9e034d27368c49f28a6
* @return x-only tapOutputKey||tapInternalKey as sub keydata, plain sigining participant keys as valuedata
*/
export function encodePsbtMusig2Participants(participants: PsbtMusig2Participants): ProprietaryKeyValue {
const keydata = [participants.tapOutputKey, participants.tapInternalKey].map((pubkey) => checkXOnlyPublicKey(pubkey));
const value = participants.participantPubKeys.map((pubkey) => checkPlainPublicKey(pubkey));
const key = {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PARTICIPANT_PUB_KEYS,
keydata: Buffer.concat(keydata),
};
return { key, value: Buffer.concat(value) };
}
/**
* Psbt proprietary key val util function for pub nonce. SubType is 0x02
* Ref: https://gist.github.com/sanket1729/4b525c6049f4d9e034d27368c49f28a6
* @return plain-participantPubKey||x-only-tapOutputKey as sub keydata, 66 bytes of 2 pub nonces as valuedata
*/
export function encodePsbtMusig2PubNonce(nonce: PsbtMusig2PubNonce): ProprietaryKeyValue {
if (nonce.pubNonce.length !== 66) {
throw new Error(`Invalid pubNonces length ${nonce.pubNonce.length}`);
}
const keydata = Buffer.concat([
checkPlainPublicKey(nonce.participantPubKey),
checkXOnlyPublicKey(nonce.tapOutputKey),
]);
const key = {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
keydata,
};
return { key, value: nonce.pubNonce };
}
export function encodePsbtMusig2PartialSig(partialSig: PsbtMusig2PartialSig): ProprietaryKeyValue {
if (partialSig.partialSig.length !== 32 && partialSig.partialSig.length !== 33) {
throw new Error(`Invalid partialSig length ${partialSig.partialSig.length}`);
}
const keydata = Buffer.concat([
checkPlainPublicKey(partialSig.participantPubKey),
checkXOnlyPublicKey(partialSig.tapOutputKey),
]);
const key = {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PARTIAL_SIG,
keydata,
};
return { key, value: partialSig.partialSig };
}
/**
* Decodes proprietary key value data for participant pub keys
* @param kv
*/
export function decodePsbtMusig2Participants(kv: ProprietaryKeyValue): PsbtMusig2Participants {
if (
kv.key.identifier !== PSBT_PROPRIETARY_IDENTIFIER ||
kv.key.subtype !== ProprietaryKeySubtype.MUSIG2_PARTICIPANT_PUB_KEYS
) {
throw new Error(`Invalid identifier ${kv.key.identifier} or subtype ${kv.key.subtype} for participants pub keys`);
}
const key = kv.key.keydata;
if (key.length !== 64) {
throw new Error(`Invalid keydata size ${key.length} for participant pub keys`);
}
const value = kv.value;
if (value.length !== 66) {
throw new Error(`Invalid valuedata size ${value.length} for participant pub keys`);
}
const participantPubKeys: Tuple<Buffer> = [value.subarray(0, 33), value.subarray(33)];
if (participantPubKeys[0].equals(participantPubKeys[1])) {
throw new Error(`Duplicate participant pub keys found`);
}
return { tapOutputKey: key.subarray(0, 32), tapInternalKey: key.subarray(32), participantPubKeys };
}
/**
* Decodes proprietary key value data for musig2 nonce
* @param kv
*/
export function decodePsbtMusig2Nonce(kv: ProprietaryKeyValue): PsbtMusig2PubNonce {
if (kv.key.identifier !== PSBT_PROPRIETARY_IDENTIFIER || kv.key.subtype !== ProprietaryKeySubtype.MUSIG2_PUB_NONCE) {
throw new Error(`Invalid identifier ${kv.key.identifier} or subtype ${kv.key.subtype} for nonce`);
}
const key = kv.key.keydata;
if (key.length !== 65) {
throw new Error(`Invalid keydata size ${key.length} for nonce`);
}
const value = kv.value;
if (value.length !== 66) {
throw new Error(`Invalid valuedata size ${value.length} for nonce`);
}
return { participantPubKey: key.subarray(0, 33), tapOutputKey: key.subarray(33), pubNonce: value };
}
/**
* Decodes proprietary key value data for musig2 partial sig
* @param kv
*/
export function decodePsbtMusig2PartialSig(kv: ProprietaryKeyValue): PsbtMusig2PartialSig {
if (
kv.key.identifier !== PSBT_PROPRIETARY_IDENTIFIER ||
kv.key.subtype !== ProprietaryKeySubtype.MUSIG2_PARTIAL_SIG
) {
throw new Error(`Invalid identifier ${kv.key.identifier} or subtype ${kv.key.subtype} for partial sig`);
}
const key = kv.key.keydata;
if (key.length !== 65) {
throw new Error(`Invalid keydata size ${key.length} for partial sig`);
}
const value = kv.value;
if (value.length !== 32 && value.length !== 33) {
throw new Error(`Invalid valuedata size ${value.length} for partial sig`);
}
return { participantPubKey: key.subarray(0, 33), tapOutputKey: key.subarray(33), partialSig: value };
}
export function createTapInternalKey(plainPubKeys: Buffer[]): Buffer {
return Buffer.from(musig.getXOnlyPubkey(musig.keyAgg(plainPubKeys)));
}
export function createTapOutputKey(internalPubKey: Buffer, tapTreeRoot: Buffer): Buffer {
return Buffer.from(
tapTweakPubkey(ecc, toXOnlyPublicKey(internalPubKey), checkTapMerkleRoot(tapTreeRoot)).xOnlyPubkey
);
}
export function createAggregateNonce(pubNonces: Tuple<Buffer>): Buffer {
return Buffer.from(musig.nonceAgg(pubNonces));
}
export function createTapTweak(tapInternalKey: Buffer, tapMerkleRoot: Buffer): Buffer {
return Buffer.from(calculateTapTweak(checkXOnlyPublicKey(tapInternalKey), checkTapMerkleRoot(tapMerkleRoot)));
}
function startMusig2SigningSession(
aggNonce: Buffer,
hash: Buffer,
publicKeys: Tuple<Buffer>,
tweak: Buffer
): SessionKey {
return musig.startSigningSession(aggNonce, hash, publicKeys, { tweak, xOnly: true });
}
export function musig2PartialSign(
privateKey: Buffer,
publicNonce: Uint8Array,
sessionKey: SessionKey,
nonceStore: Musig2NonceStore
): Buffer {
checkTxHash(Buffer.from(sessionKey.msg));
return Buffer.from(
musig.partialSign({
secretKey: privateKey,
publicNonce: nonceStore.getRef(publicNonce),
sessionKey,
})
);
}
export function musig2PartialSigVerify(
sig: Buffer,
publicKey: Buffer,
publicNonce: Buffer,
sessionKey: SessionKey
): boolean {
checkTxHash(Buffer.from(sessionKey.msg));
return musig.partialVerify({ sig, publicKey, publicNonce, sessionKey });
}
export function musig2AggregateSigs(sigs: Buffer[], sessionKey: SessionKey): Buffer {
return Buffer.from(musig.signAgg(sigs, sessionKey));
}
/** @return session key that can be used to reference the session later */
export function createMusig2SigningSession(sessionArgs: {
pubNonces: Tuple<Buffer>;
txHash: Buffer;
pubKeys: Tuple<Buffer>;
internalPubKey: Buffer;
tapTreeRoot: Buffer;
}): SessionKey {
checkTxHash(sessionArgs.txHash);
const aggNonce = createAggregateNonce(sessionArgs.pubNonces);
const tweak = createTapTweak(sessionArgs.internalPubKey, sessionArgs.tapTreeRoot);
return startMusig2SigningSession(aggNonce, sessionArgs.txHash, sessionArgs.pubKeys, tweak);
}
/**
* @returns psbt proprietary key for musig2 participant key value data
* If no key value exists, undefined is returned.
*/
export function parsePsbtMusig2Participants(input: PsbtInput): PsbtMusig2Participants | undefined {
const participantsKeyVals = getPsbtInputProprietaryKeyVals(input, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PARTICIPANT_PUB_KEYS,
});
if (!participantsKeyVals.length) {
return undefined;
}
if (participantsKeyVals.length > 1) {
throw new Error(`Found ${participantsKeyVals.length} matching participant key value instead of 1`);
}
return decodePsbtMusig2Participants(participantsKeyVals[0]);
}
/**
* @returns psbt proprietary key for musig2 public nonce key value data
* If no key value exists, undefined is returned.
*/
export function parsePsbtMusig2Nonces(input: PsbtInput): PsbtMusig2PubNonce[] | undefined {
const nonceKeyVals = getPsbtInputProprietaryKeyVals(input, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
});
if (!nonceKeyVals.length) {
return undefined;
}
if (nonceKeyVals.length > 2) {
throw new Error(`Found ${nonceKeyVals.length} matching nonce key value instead of 1 or 2`);
}
return nonceKeyVals.map((kv) => decodePsbtMusig2Nonce(kv));
}
/**
* @returns psbt proprietary key for musig2 partial sig key value data
* If no key value exists, undefined is returned.
*/
export function parsePsbtMusig2PartialSigs(input: PsbtInput): PsbtMusig2PartialSig[] | undefined {
const sigKeyVals = getPsbtInputProprietaryKeyVals(input, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PARTIAL_SIG,
});
if (!sigKeyVals.length) {
return undefined;
}
if (sigKeyVals.length > 2) {
throw new Error(`Found ${sigKeyVals.length} matching partial signature key value instead of 1 or 2`);
}
return sigKeyVals.map((kv) => decodePsbtMusig2PartialSig(kv));
}
/**
* Assert musig2 participant key value data with tapInternalKey and tapMerkleRoot.
* <tapOutputKey><tapInputKey> => <participantKey1><participantKey2>
* Using tapMerkleRoot and 2 participant keys, the tapInputKey is validated and using tapMerkleRoot and tapInputKey,
* the tapOutputKey is validated.
*/
export function assertPsbtMusig2Participants(
participantKeyValData: PsbtMusig2Participants,
tapInternalKey: Buffer,
tapMerkleRoot: Buffer
): void {
checkXOnlyPublicKey(tapInternalKey);
checkTapMerkleRoot(tapMerkleRoot);
const participantPubKeys = participantKeyValData.participantPubKeys;
const internalKey = createTapInternalKey(participantPubKeys);
if (!internalKey.equals(participantKeyValData.tapInternalKey)) {
throw new Error('Invalid participants keydata tapInternalKey');
}
const outputKey = createTapOutputKey(internalKey, tapMerkleRoot);
if (!outputKey.equals(participantKeyValData.tapOutputKey)) {
throw new Error('Invalid participants keydata tapOutputKey');
}
if (!internalKey.equals(tapInternalKey)) {
throw new Error('tapInternalKey and aggregated participant pub keys does not match');
}
}
/**
* Assert musig2 public nonce key value data with participant key value data
* (refer assertPsbtMusig2ParticipantsKeyValData).
* <participantKey1><tapOutputKey> => <pubNonce1>
* <participantKey2><tapOutputKey> => <pubNonce2>
* Checks against participant keys and tapOutputKey
*/
export function assertPsbtMusig2Nonces(
noncesKeyValData: PsbtMusig2PubNonce[],
participantKeyValData: PsbtMusig2Participants
): void {
checkXOnlyPublicKey(participantKeyValData.tapOutputKey);
participantKeyValData.participantPubKeys.forEach((kv) => checkPlainPublicKey(kv));
if (participantKeyValData.participantPubKeys[0].equals(participantKeyValData.participantPubKeys[1])) {
throw new Error(`Duplicate participant pub keys found`);
}
if (noncesKeyValData.length > 2) {
throw new Error(`Invalid nonce key value count ${noncesKeyValData.length}`);
}
noncesKeyValData.forEach((nonceKv) => {
const index = participantKeyValData.participantPubKeys.findIndex((pubKey) =>
nonceKv.participantPubKey.equals(pubKey)
);
if (index < 0) {
throw new Error('Invalid nonce keydata participant pub key');
}
if (!nonceKv.tapOutputKey.equals(participantKeyValData.tapOutputKey)) {
throw new Error('Invalid nonce keydata tapOutputKey');
}
});
}
/**
* @returns Input object but sig hash type data is taken out from partialSig field.
* If sig hash type is not common for all sigs, error out, otherwise returns the modified object and single hash type.
*/
export function getSigHashTypeFromSigs(partialSigs: PsbtMusig2PartialSig[]): {
partialSigs: PsbtMusig2PartialSig[];
sigHashType: number;
} {
if (!partialSigs.length) {
throw new Error('partialSigs array can not be empty');
}
const pSigsWithHashType = partialSigs.map((kv) => {
const { partialSig, participantPubKey, tapOutputKey } = kv;
return partialSig.length === 33
? { pSig: { partialSig: partialSig.slice(0, 32), participantPubKey, tapOutputKey }, sigHashType: partialSig[32] }
: { pSig: { partialSig, participantPubKey, tapOutputKey }, sigHashType: Transaction.SIGHASH_DEFAULT };
});
const sigHashType = pSigsWithHashType[0].sigHashType;
if (!pSigsWithHashType.every((pSig) => pSig.sigHashType === sigHashType)) {
throw new Error('signatures must use same sig hash type');
}
return { partialSigs: pSigsWithHashType.map(({ pSig }) => pSig), sigHashType };
}
export function createMusig2DeterministicNonce(params: PsbtMusig2DeterministicParams): Buffer {
return Buffer.from(
musig.deterministicNonceGen({
secretKey: params.privateKey,
aggOtherNonce: musig.nonceAgg([params.otherNonce]),
publicKeys: params.publicKeys,
tweaks: [{ tweak: createTapTweak(params.internalPubKey, params.tapTreeRoot), xOnly: true }],
msg: params.hash,
}).publicNonce
);
}
export function musig2DeterministicSign(params: PsbtMusig2DeterministicParams): {
sig: Buffer;
sessionKey: SessionKey;
publicNonce: Buffer;
} {
const { sig, sessionKey, publicNonce } = musig.deterministicSign({
secretKey: params.privateKey,
aggOtherNonce: musig.nonceAgg([params.otherNonce]),
publicKeys: params.publicKeys,
tweaks: [{ tweak: createTapTweak(params.internalPubKey, params.tapTreeRoot), xOnly: true }],
msg: params.hash,
});
return { sig: Buffer.from(sig), sessionKey, publicNonce: Buffer.from(publicNonce) };
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!