Skip to content

Commit

Permalink
add MuSig related bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 committed May 22, 2023
1 parent 5d1cd1d commit 3ebb05c
Show file tree
Hide file tree
Showing 6 changed files with 844 additions and 6 deletions.
2 changes: 1 addition & 1 deletion dist/secp256k1-zkp.js

Large diffs are not rendered by default.

356 changes: 356 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,348 @@ module.exports = () => {
return null;
}

/**
* @typedef {Object} AggregatedPublicKey
* @property {Uint8Array} aggPubkey aggregated public key
* @property {Uint8Array} keyaggCache cache for public key aggregation
*/
/**
* @summary aggregate public keys
* @arg {Array} pubkeys public keys to aggregate
* @returns {AggregatedPublicKey}
*/
function musigPubkeyAgg(pubkeys) {
if (!pubkeys || !pubkeys.length) {
throw new TypeError('pubkeys must be an Array');
}
for (const pubkey of pubkeys) {
if (!(pubkey instanceof Uint8Array)) {
throw new TypeError('all elements of pubkeys must be Uint8Array');
}
}

const aggPubkey = malloc(32);
const keyaggCache = malloc(165);

const ret = Module.ccall(
'musig_pubkey_agg',
'number',
['number', 'number', 'number', 'number'],
[aggPubkey, keyaggCache, charStarArray(pubkeys), pubkeys.length]
);

if (ret === 1) {
const res = {
aggPubkey: charStarToUint8(aggPubkey, 32),
keyaggCache: charStarToUint8(keyaggCache, 165),
};
freeMalloc();
return res;
}

freeMalloc();
throw new Error('musig_pubkey_agg', ret);
}

/**
* @typedef {Object} Nonces
* @property {Uint8Array} secnonce secret nonce
* @property {Uint8Array} pubnonce public nonce
*/
/**
* @summary generate MuSig nonces
* @arg {Uint8Array} sessionId entropy for nonce generation
* @returns {Nonces}
*/
function musigNonceGen(sessionId) {
if (!(sessionId instanceof Uint8Array)) {
throw new TypeError('sessionId must be Uint8Array');
}

const secnonce = malloc(68);
const pubnonce = malloc(66);

const ret = Module.ccall(
'musig_nonce_gen',
'number',
['number', 'number', 'number'],
[secnonce, pubnonce, charStar(sessionId)]
);

if (ret === 1) {
const res = {
secnonce: charStarToUint8(secnonce, 68),
pubnonce: charStarToUint8(pubnonce, 66),
};
freeMalloc();
return res;
}

freeMalloc();
throw new Error('musig_nonce_gen', ret);
}

/**
* @summary aggregate nonces of a MuSig signing session
* @arg {Array} pubnonces public nonces to aggregate
* @returns {Uint8Array} aggregated public nonce
*/
function musigNonceAgg(pubnonces) {
if (!pubnonces || !pubnonces.length) {
throw new TypeError('pubnonces must be an Array');
}
for (const pubnonce of pubnonces) {
if (!(pubnonce instanceof Uint8Array)) {
throw new TypeError('all elements of pubnonces must be Uint8Array');
}
}

const aggnonce = malloc(66);

const ret = Module.ccall(
'musig_nonce_agg',
'number',
['number', 'number', 'number'],
[aggnonce, charStarArray(pubnonces), pubnonces.length]
);

if (ret === 1) {
const res = charStarToUint8(aggnonce, 66);
freeMalloc();
return res;
}

freeMalloc();
throw new Error('musig_nonce_gen', ret);
}

/**
* @summary compute session for MuSig signing and partial signature verification
* @arg {Uint8Array} nonceAgg aggregated public nonce
* @arg {Uint8Array} msg message to sign
* @arg {Uint8Array} keyaggCache cache for public key aggregation
* @returns {Uint8Array} MuSig session
*/
function musigNonceProcess(nonceAgg, msg, keyaggCache) {
if (!(nonceAgg instanceof Uint8Array)) {
throw new TypeError('nonceAgg must be Uint8Array');
}
if (!(msg instanceof Uint8Array)) {
throw new TypeError('msg must be Uint8Array');
}
if (!(keyaggCache instanceof Uint8Array)) {
throw new TypeError('keyaggCache must be Uint8Array');
}

const session = malloc(133);

const ret = Module.ccall(
'musig_nonce_process',
'number',
['number', 'number', 'number', 'number'],
[session, charStar(nonceAgg), charStar(msg), charStar(keyaggCache)]
);

if (ret == 1) {
const res = charStarToUint8(session, 133);
freeMalloc();
return res;
}

freeMalloc();
throw new Error('musig_nonce_process', ret);
}

/**
* @summary produce a partial signature
* @arg {Uint8Array} secnonce secret nonce
* @arg {Uint8Array} seckey secret (private) key to sign with
* @arg {Uint8Array} keyaggCache cache for public key aggregation
* @arg {Uint8Array} session MuSig session
* @returns {Uint8Array} partial signature
*/
function musigPartialSign(secnonce, seckey, keyaggCache, session) {
if (!(secnonce instanceof Uint8Array)) {
throw new TypeError('secnonce must be Uint8Array');
}
if (!(seckey instanceof Uint8Array)) {
throw new TypeError('seckey must be Uint8Array');
}
if (!(keyaggCache instanceof Uint8Array)) {
throw new TypeError('keyaggCache must be Uint8Array');
}
if (!(session instanceof Uint8Array)) {
throw new TypeError('session must be Uint8Array');
}

const partialSig = malloc(32);

const ret = Module.ccall(
'musig_partial_sign',
'number',
['number', 'number', 'number', 'number', 'number'],
[
partialSig,
charStar(secnonce),
charStar(seckey),
charStar(keyaggCache),
charStar(session),
]
);

if (ret === 1) {
const res = charStarToUint8(partialSig, 32);
freeMalloc();
return res;
}

freeMalloc();
throw new Error('musig_partial_sign', ret);
}

/**
* @summary verify a partial signature
* @arg {Uint8Array} partialSig partial signature
* @arg {Uint8Array} pubnonce public nonce
* @arg {Uint8Array} pubkey public key of the secret key the signature was created with
* @arg {Uint8Array} keyaggCache cache for public key aggregation
* @arg {Uint8Array} session MuSig session
* @returns {boolean} whether the signature successfully verified
*/
function musigPartialVerify(
partialSig,
pubnonce,
pubkey,
keyaggCache,
session
) {
if (!(partialSig instanceof Uint8Array)) {
throw new TypeError('partialSig must be Uint8Array');
}
if (!(pubnonce instanceof Uint8Array)) {
throw new TypeError('pubnonce must be Uint8Array');
}
if (!(pubkey instanceof Uint8Array)) {
throw new TypeError('pubkey must be Uint8Array');
}
if (!(keyaggCache instanceof Uint8Array)) {
throw new TypeError('keyaggCache must be Uint8Array');
}
if (!(session instanceof Uint8Array)) {
throw new TypeError('session must be Uint8Array');
}

const ret = Module.ccall(
'musig_partial_sig_verify',
'number',
['number', 'number', 'number', 'number', 'number'],
[
charStar(partialSig),
charStar(pubnonce),
charStar(pubkey),
charStar(keyaggCache),
charStar(session),
]
);

freeMalloc();
// To return true when the signatures is verified successfully
return ret === 1;
}

/**
* @summary aggregate partial signatures
* @arg {Uint8Array} session MuSig session
* @arg {Array} partialSigs partial signatures to aggregate
* @returns {Uint8Array} aggregated signature
*/
function musigPartialSigAgg(session, partialSigs) {
if (!(session instanceof Uint8Array)) {
throw new TypeError('session must be Uint8Array');
}
if (!partialSigs || !partialSigs.length) {
throw new TypeError('partialSigs must be an Array');
}
for (const partialSig of partialSigs) {
if (!(partialSig instanceof Uint8Array)) {
throw new TypeError(
'all elements of partialSigs must be Uint8Array'
);
}
}

const sig = malloc(64);

const ret = Module.ccall(
'musig_partial_sig_agg',
['number'],
['number', 'number', 'number', 'number'],
[
sig,
charStar(session),
charStarArray(partialSigs),
partialSigs.length,
]
);

if (ret === 1) {
const res = charStarToUint8(sig, 64);
freeMalloc();
return res;
}

freeMalloc();
throw new Error('musig_partial_sig_agg', ret);
}

/**
* @summary apply x-only tweaking to a Musig public key
* @arg {Uint8Array} keyaggCache cache for public key aggregation
* @arg {Uint8Array} tweak 32-byte tweak to apply
* @arg {boolean} compress whether the tweaked public key should be compressed
* @returns {Uint8Array} tweaked public key
*/
function musigPubkeyXonlyTweakAdd(keyaggCache, tweak, compress = true) {
if (!(keyaggCache instanceof Uint8Array)) {
throw new TypeError('keyaggCache must be Uint8Array');
}
if (!(tweak instanceof Uint8Array)) {
throw new TypeError('tweak must be Uint8Array');
}
if (typeof compress !== 'boolean') {
throw new TypeError('compress must be boolean');
}

const output = malloc(65);
const outputLen = malloc(8);
Module.setValue(outputLen, 65, 'i64');

const ret = Module.ccall(
'musig_pubkey_xonly_tweak_add',
'number',
['number', 'number', 'number', 'number', 'number'],
[
output,
outputLen,
compress ? 1 : 0,
charStar(keyaggCache),
charStar(tweak),
]
);

if (ret === 1) {
const res = charStarToUint8(
output,
Module.getValue(outputLen, 'i64')
);
freeMalloc();
return res;
}

freeMalloc();
throw new Error('musig_pubkey_xonly_tweak_add', ret);
}

function Uint64Long(ptr) {
return new Long(
Module.getValue(ptr, 'i32'),
Expand Down Expand Up @@ -1299,6 +1641,10 @@ module.exports = () => {
return ptr;
}

function charStarToUint8(ptr, size) {
return new Uint8Array(Module.HEAPU8.subarray(ptr, ptr + size));
}

resolve({
ecdh,
ecc: {
Expand Down Expand Up @@ -1336,6 +1682,16 @@ module.exports = () => {
generate: surjectionProofGenerate,
verify: surjectionProofVerify,
},
musig: {
nonceAgg: musigNonceAgg,
nonceGen: musigNonceGen,
pubkeyAgg: musigPubkeyAgg,
partialSign: musigPartialSign,
nonceProcess: musigNonceProcess,
partialSigAgg: musigPartialSigAgg,
partialVerify: musigPartialVerify,
pubkeyXonlyTweakAdd: musigPubkeyXonlyTweakAdd,
},
});
});
});
Expand Down
Loading

0 comments on commit 3ebb05c

Please sign in to comment.