Skip to content

Commit

Permalink
toward Atomic Swaps + reorganization
Browse files Browse the repository at this point in the history
  • Loading branch information
rdubois-crypto committed Nov 28, 2024
1 parent 59a34e5 commit c728c2c
Show file tree
Hide file tree
Showing 3 changed files with 307 additions and 29 deletions.
38 changes: 10 additions & 28 deletions src/libMPC/SCL_Musig2.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@
/* License: This software is licensed under MIT License
/********************************************************************************************/

import { createHash } from 'crypto';
import { ed25519 } from '@noble/curves/ed25519';
import{reverse, bytes_xor, int_from_bytes, int_to_bytes} from "./common.mjs";
import { tagged_hashBTC } from './bip327.mjs';
import{reverse, bytes_xor, int_from_bytes, int_to_bytes, tagged_hashBTC, taghash_rfc8032} from "./common.mjs";

import { secp256k1 } from '@noble/curves/secp256k1';
import { etc, utils, getPublicKey } from '@noble/secp256k1';
import{SCL_ecc} from './SCL_ecc.mjs';
import { randomBytes } from 'crypto'; // Use Node.js's crypto module


/********************************************************************************************/
/* CLASS MUSIG2 */
/********************************************************************************************/
// Utility to handle different curves
export class SCL_Musig2
{
Expand Down Expand Up @@ -179,7 +182,6 @@ export class SCL_Musig2

// Compute the tagged hash with 'MuSig/nonce' as the tag
const hash = this.TagHash('MuSig/nonce', buf);

// Return the result as a BigInt
return hash;
}
Expand Down Expand Up @@ -462,32 +464,12 @@ Psign(secnonce, sk, session_ctx){
}


}//end of class Musig2

// Function to compute sha256 hash
export function sha512(data) {
return createHash('sha512').update(data).digest();
}


//look at endianness error
export function taghash_rfc8032(tag, message){
// Convert the tag to a sha256 hash (as bytes)
const U8tag = Buffer.from(tag, 'utf-8');

// Concatenate (encodePacked) tagHash, tagHash, and the message
let encoded = Buffer.concat([U8tag, message]);

// Compute final sha256 hash
let finalHash = sha512(encoded);
//swap then reduce mod q (damned endians)
finalHash=finalHash.reverse();

finalHash= int_from_bytes(finalHash) % ed25519.CURVE.n;
}
/********************************************************************************************/
/* END OF CLASS MUSIG2 */
/********************************************************************************************/


return int_to_bytes(finalHash,32);
}



Expand Down
239 changes: 239 additions & 0 deletions src/libMPC/SCL_atomic_swaps.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
/********************************************************************************************/
/*
/* ___ _ _ ___ _ _ _ _
/* / __|_ __ ___ ___| |_| |_ / __|_ _ _ _ _ __| |_ ___ | | (_) |__
/* \__ \ ' \/ _ \/ _ \ _| ' \ | (__| '_| || | '_ \ _/ _ \ | |__| | '_ \
/* |___/_|_|_\___/\___/\__|_||_| \___|_| \_, | .__/\__\___/ |____|_|_.__/
/* |__/|_|
/*
/* Copyright (C) 2024 - Renaud Dubois - This file is part of SCL (Smooth CryptoLib) project
/* License: This software is licensed under MIT License
/********************************************************************************************/
import{nonce_gen_internal, nonce_agg, key_agg, IndividualPubKey, psign, partial_sig_verify_internal} from './bip327.mjs'

import { utils, getPublicKey } from '@noble/secp256k1';


import{SCL_ecc} from './SCL_ecc.mjs';
import{SCL_Musig2} from './SCL_Musig2.mjs';

import { randomBytes } from 'crypto'; // Use Node.js's crypto module



//the Alice adaptator signature
//sk_A is Alice secret Key
//rA is the secnonce
//R is the public agg nonce
//Pub_AB is the Musig2 agreed multisig key between Alice and Bob
//Pub_A is Alice public key

export function psign_adapt(psig, t){

sprime=(int_from_bytes(psig)+int_from_bytes(t)) % secp256k1.CURVE.n;

return sprime;
}

//check that a tweaked partially signature is valid
export function partial_adaptatorsig_verify_internal(psig, pubnonce, pk, session_ctx, T){

return true;
}

export function atomic_check(tG, psA1, psA2, psB1, psB2, QA, R, msg1, msg2){

return true;
}


export function get_tweak_from_sigs(sAp, sB, sAB)
{
const sABp=partial_sig_agg([sAp, sB]);
t=(sABp-sAB)% secp256k1.CURVE.n;
return t;
}

//the function takes as input an adaptator signature, its tweak t, and a valid signature, and returns the Musig2 corresponding signature
export function sign_untweak(t, psigA_adapt, psigB){
const sABp=partial_sig_agg([psigA_adapt, psigB]);

const sAB= (sABp - t)% secp256k1.CURVE.n;

return sAB;
}

export function atomic_example(){

// Alice and Bob private keys
const skA = utils.randomPrivateKey();
console.log("sk", skA);
const skB = utils.randomPrivateKey();

//Alice and Bob public keys
const QA= IndividualPubKey(skA);
const QB= IndividualPubKey(skB);

const pubkeys=[QA, QB];

const msg1=Buffer.from("Unlock 1strkBTC on Starknet to Alice",'utf-8');
console.log("msg1=",msg1);
const msg2=Buffer.from("Unlock 1WBTC on Ethereum to Bob",'utf-8');


//key aggregation
const QAB=key_agg([QA, QB])[0];
console.log("QAB=",QAB);

//nonce generation

const nonceA1=nonce_gen_internal(utils.randomPrivateKey(), skA, QA, QAB, msg1, Buffer.from(""));//alice generates its nonce
const nonceA2=nonce_gen_internal(utils.randomPrivateKey(), skA, QA, QAB, msg2, Buffer.from(""));//alice generates its nonce

const nonceB1=nonce_gen_internal(utils.randomPrivateKey(), skB, QB, QAB, msg1, Buffer.from(""));//alice generates its nonce
const nonceB2=nonce_gen_internal(utils.randomPrivateKey(), skB, QB, QAB, msg2, Buffer.from(""));//alice generates its nonce


//alice and Bob construct common nonce from pubnonces
const R1=nonce_agg([nonceA1[1], nonceB1[1]]);
const R2=nonce_agg([nonceA2[1], nonceB2[1]]);

session_ctx1=[R1, pubkeys, [], [], msg1];
session_ctx2=[R1, pubkeys, [], [], msg2];


//Alice locks one BTC on Ethereum, using the corresponding Musig2 adress, it is unlocked with msg2 multisig or timelock expiration
//bob locks one BTC on starknet, using the corresponding Musig2 adress, it is unlocked with msg1 multisig or timelock expiration

//alice generates a secret adaptator tweak and publish offchain the value tG
const t = utils.randomPrivateKey();

//alice generates the secret adaptator signatures sA'1 and sA'2 for both message and broadcast them offchain
const psigA1=psign(nonceA1[0], skA, session_ctx1)
const sAp1=psign_adapt(psigA1, t);
const psigA2=psign(nonceA2[0], skA, session_ctx1)
const sAp2=psign_adapt(psigA2, t);

//bob check the compliance, then broadcast offchain signature of message 1 sb1
psigB1=psign(nonceB1, skB, session_ctx1);

//Alice unlocks its strkBTC, using sb1, thus revealing the tweak to Bob
sAB1=partial_sig_agg([psigA1, psigB1], session_ctx1);

//Bob reads onchain 1 the value of t, then compute the value of SAB2, unlocking its token
const rec_t=get_tweak_from_sigs(sAB1, sAp1, psigB1);
const sAB2=sign_untweak( psigA2);

}

export class SCL_Atomic_Swap
{
constructor(curve) {
this.signer=new SCL_Musig2(curve);

this.curve=signer.curve;
}

Psign_adapt(psig, t){


let sprime=(int_from_bytes(psig)+t ) % this.curve.order;
let G= this.curve.GetBase(t);

return [sprime, G.multiply(t)];
}

Untweak(t, psigA_adapt, psigB){
const sABp=partial_sig_agg([psigA_adapt, psigB]);

const sAB= (sABp - t)% secp256k1.CURVE.n;

return sAB;
}

}


function test_full_atomic_session(curve){
const swapper= new SCL_Atomic_Swap(curve);
const signer = swapper.signer;

console.log("/*************************** ");
console.log("Test full Atomic session on curve", Curve);

console.log(" -Generate random keys");

const sk1=signer.curve.Get_Random_privateKey();//this provides a 32 bytes array
const sk2=signer.curve.Get_Random_privateKey();

console.log("sk1=",sk1 );
console.log("sk2=",sk2 );
let seckeys=[sk1, sk2];

const pubK1=signer.IndividualPubKey_array(sk1);
const pubK2=signer.IndividualPubKey_array(sk2);

console.log("pubK1=",pubK1 );
console.log("pubK2=",pubK2 );

const pubkeys=[pubK1, pubK2];

let aggpk = signer.Key_agg(pubkeys)[0];//here aggpk is a 32 or 33 bytes compressed public key
let x_aggpk=signer.curve.ForceXonly(aggpk);//x-only version for noncegen, allways 32

console.log("Aggregated Pubkey:", aggpk);

const msg1=Buffer.from("Unlock 1strkBTC on Starknet to Alice",'utf-8');
console.log("msg1=",msg1);
const msg2=Buffer.from("Unlock 1WBTC on Ethereum to Bob",'utf-8');
console.log("msg2=",msg2);


//nonce generation
console.log(" -Generate random Nonces with commitment", aggpk);
//diversification chain
const extra_in1= Buffer.from(randomBytes(32));
const extra_in2= Buffer.from(randomBytes(32));


let nonceA1= signer.Nonce_gen(seckeys[0], pubkeys[0], x_aggpk, msg1, extra_in1);
let nonceB1= signer.Nonce_gen(seckeys[1], pubkeys[1], x_aggpk, msg2, extra_in1);

let nonceA2= signer.Nonce_gen(seckeys[0], pubkeys[0], x_aggpk, msg1, extra_in2);
let nonceB2= signer.Nonce_gen(seckeys[1], pubkeys[1], x_aggpk, msg2, extra_in2);


//aggregation of public nonces
let aggnonce1 = signer.Nonce_agg([nonceA1[1].toString('hex'), nonceB1[1].toString('hex')]);
let aggnonce2 = signer.Nonce_agg([nonceA2[1].toString('hex'), nonceB2[1].toString('hex')]);


const session_ctx1=[aggnonce1, pubkeys, [], [], msg];
const session_ctx2=[aggnonce2, pubkeys, [], [], msg];

let pA1=signer.Psign(nonceA1[0], seckeys[0], session_ctx1);
let pA2=signer.Psign(nonceA2[0], seckeys[0], session_ctx2);


let pB1=signer.Psign(nonceB1[0], seckeys[0], session_ctx1);
let pB2=signer.Psign(nonceB2[0], seckeys[0], session_ctx2);

//Alice tweaks signatures
let t=int_from_bytes(signer.curve.Get_Random_privateKey());
let atomic_ctx1=swapper.Psign_adapt(pA1, t);
let atomic_ctx2=swapper.Psign_adapt(pA2, t);

//todo:Bob checks compliance of tG, pA2p, pA1p, msg1, msg2


//Bob compute sAB', then substract to obtain sAB
let sAB1=swapper.Untweak(t,atomic_ctx1 );

//




console.log("p1=",p1);

}
59 changes: 58 additions & 1 deletion src/libMPC/common.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
/* License: This software is licensed under MIT License
/********************************************************************************************/

import { createHash } from 'crypto';


export function bytes_xor(a,b){
if (a.length !== b.length) {
throw new Error('Byte arrays must be of the same length');
Expand All @@ -21,6 +24,9 @@ export function bytes_xor(a,b){
return c;
}

/********************************************************************************************/
/* ENCODINGS */
/********************************************************************************************/
//convert bytes to bigInt
export function int_from_bytes(bytes){
return BigInt('0x' + Buffer.from(bytes).toString('hex'));
Expand Down Expand Up @@ -63,4 +69,55 @@ export function int_to_bytes(value, byteLength){
//reverse the byte endianness of a buffer (mirroring from/to lsb/msb)
export function reverse(msb){
return Buffer.from([...msb].reverse());
}
}

/********************************************************************************************/
/* HASHES */
/********************************************************************************************/
// Tagged hash function compliant with BIP327
// tag: str
// message: bytes
export function tagged_hashBTC(tag, message) {
// Convert the tag to a sha256 hash (as bytes)
const tagHash = sha256(Buffer.from(tag, 'utf-8'));

// Concatenate (encodePacked) tagHash, tagHash, and the message
const encoded = Buffer.concat([tagHash, tagHash, message]);

// Compute final sha256 hash
const finalHash = sha256(encoded);

return finalHash;
}


//look at endianness error
export function taghash_rfc8032(tag, message){
// Convert the tag to a sha256 hash (as bytes)
const U8tag = Buffer.from(tag, 'utf-8');

// Concatenate (encodePacked) tagHash, tagHash, and the message
let encoded = Buffer.concat([U8tag, message]);

// Compute final sha256 hash
let finalHash = sha512(encoded);
//swap then reduce mod q (damned endians)
finalHash=finalHash.reverse();

finalHash= int_from_bytes(finalHash) % BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989');


return int_to_bytes(finalHash,32);
}


// Function to compute sha256 hash
export function sha256(data) {
return createHash('sha256').update(data).digest();
}


// Function to compute sha256 hash
export function sha512(data) {
return createHash('sha512').update(data).digest();
}

0 comments on commit c728c2c

Please sign in to comment.