Skip to content

Commit

Permalink
Atomic swap: Psign and tweak
Browse files Browse the repository at this point in the history
  • Loading branch information
rdubois-crypto committed Nov 29, 2024
1 parent 3249486 commit b70dbc7
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 73 deletions.
184 changes: 111 additions & 73 deletions src/libMPC/SCL_atomic_swaps.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import{nonce_gen_internal, nonce_agg, key_agg, IndividualPubKey, psign, partial_

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

import{int_from_bytes, int_to_bytes} from "./common.mjs";

import{SCL_ecc} from './SCL_ecc.mjs';
import{SCL_Musig2} from './SCL_Musig2.mjs';
Expand Down Expand Up @@ -130,9 +131,9 @@ export function atomic_example(){

}

function Psign_adapt(psig, t){
function Psign_adapt(curve, psig, t){

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

return sprime;
}
Expand All @@ -146,22 +147,70 @@ function Untweak(t, psigA_adapt, psigB){
}


//verify one of the partial signature provided by a participant
function Psig_verifyTweaked(signer, psig, pubnonce, pk, session_ctx, tG){
let sessionV=signer.Get_session_values(session_ctx);//(Q, gacc, _, b, R, e)
let s = int_from_bytes(psig);
let Q=sessionV[0];
let gacc=sessionV[1];
let b=sessionV[3];
let R=sessionV[4];
let e=sessionV[5];


let R_s1 = signer.curve.PointDecompress(pubnonce.slice(0,signer.RawBytesSize));
let R_s2 = signer.curve.PointDecompress(pubnonce.slice(signer.RawBytesSize,2*signer.RawBytesSize));

let Re_s_ =R_s1.add(R_s2.multiply(b));

let Re_s=Re_s_;

if(signer.curve.Has_even_y(R)==false)
{
Re_s=Re_s.negate();//forced to even point
}
let P=signer.curve.PointDecompress(pk);//partial input public key

let a=signer.Get_session_key_agg_coeff(session_ctx[1], pk);//session_ctx[1]=pubkeys


let g=BigInt(1);
if(signer.curve.Has_even_y(Q)==false){
g=signer.order - g;//n-1
}

g=(g*gacc) % signer.order;

let G= signer.curve.GetBase();
let P1 = (G.multiply(s));

let tmp=signer.Mulmod(e,a);
tmp=signer.Mulmod(tmp,g);//e*a*g % n
let P2=(Re_s.add(P.multiply(tmp)));
P2=P2.add(tG);

return (P1.equals(P2));
}

/********************************************************************************************/
/* INITIATOR AUTOMATA*/
/********************************************************************************************/
export class SCL_Atomic_Initiator{

constructor(curve, pubkey, sk) {
constructor(curve, sk) {

this.signer=new SCL_Musig2(curve);
this.pubkey=pubkey;
this.sk=sk;

this.pubkey=this.signer.IndividualPubKey_array(sk);


this.state="idle";

this.nonceA1=0;
this.nonceA2=0;

this.pubKeyDist=0;//the responder distant public key
this.nonceB1=0;
this.nonceB2=0;

Expand All @@ -183,46 +232,64 @@ export class SCL_Atomic_Initiator{

}

InitSession(tx1, tx2)
InitSession(tx1, tx2,pubKeyDist)
{

this. nonceA1= signer.Nonce_gen(this.sk, this.pubkey, x_aggpk, tx1, extra_in1);
this. nonceA2= signer.Nonce_gen(this.sk, this.pubkey, x_aggpk, tx2, extra_in2);
this.pubKeyDist=pubKeyDist;
console.log("input: ", this.pubkey, this.pubKeyDist);

this.aggpk = this.signer.Key_agg([this.pubkey, this.pubKeyDist])[0];

let x_aggpk=this.signer.curve.ForceXonly(this.aggpk);//x-only version for noncegen, allways 32

//anti replay through nonces
let extra_in1=this.signer.curve.Get_Random_privateKey();
let extra_in2=this.signer.curve.Get_Random_privateKey();


this.nonceA1= this.signer.Nonce_gen(this.sk, this.pubkey, x_aggpk, tx1, extra_in1);
this.nonceA2= this.signer.Nonce_gen(this.sk, this.pubkey, x_aggpk, tx2, extra_in2);
this.tx1=tx1;
this.tx2=tx2;

let Message_I1=[tx1, tx2, nonceA1[1], nonceA2[1]];
let Message_I1=[tx1, tx2, this.nonceA1[1], this.nonceA2[1], this.pubkey];

this.state="waitR1"
return Message_I1;//this message is broadcast offchain
}

//Message_R1=[aggnonce1, aggnonce2, nonceB1[1], nonceB2[1]];
PartialSign_Tweaked(Message_R1){
let Message_I2=[];

this.nonceB1=Message_R1[2];
this.nonceB2=Message_R1[3];

let aggnonce1 = this.signer.Nonce_agg([this.nonceA1[1].toString('hex'), this.nonceB1.toString('hex')]);
let aggnonce2 = this.signer.Nonce_agg([this.nonceA2[1].toString('hex'), this.nonceB2.toString('hex')]);

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

const session_ctx1=[aggnonce1, [this.pubkey, Message_R1[4]], [], [], this.tx1];
const session_ctx2=[aggnonce2, [this.pubkey, Message_R1[4]], [], [], this.tx2];
const session_ctx1=[aggnonce1, [this.pubkey, this.pubKeyDist], [], [], this.tx1];//session_ctx=[aggnonce, pubkeys, [], [], msg];
const session_ctx2=[aggnonce2, [this.pubkey, this.pubKeyDist], [], [], this.tx2];


let psigI1=signer.Psign(nonceA1[0], this.sk, session_ctx1);
let psigI2=signer.Psign(nonceA2[0], this.sk, session_ctx2);
let psigI1=this.signer.Psign(this.nonceA1[0], this.sk, session_ctx1);
console.log("Partial verify:", this.signer.Psig_verify(psigI1, this.nonceA1[1], this.pubkey, session_ctx1));
let psigI2=this.signer.Psign(this.nonceA2[0], this.sk, session_ctx2);


this.t=int_from_bytes(signer.Get_Random_privateKey());
let G= this.curve.GetBase(t);
this.tG=G.multiply(t);
this.t=int_from_bytes(this.signer.curve.Get_Random_privateKey());
let G= this.signer.curve.GetBase();
this.tG=G.multiply(this.t);


let psigI1p=Psign_adapt(psigI1,t)
let psigI2p=Psign_adapt(psigI2,t)
let psigI1p=Psign_adapt(this.signer.curve, psigI1,this.t)
let psigI2p=Psign_adapt(this.signer.curve, psigI2,this.t)

Message_I2=[psigI1p, psigI2p, this.tG];
let checkpoint=Psig_verifyTweaked(this.signer, int_to_bytes(psigI1p,32), this.nonceA1[1], this.pubkey, session_ctx1, this.tG);
console.log("verify tweaked:", checkpoint);
checkpoint=Psig_verifyTweaked(this.signer, int_to_bytes(psigI2p,32), this.nonceA2[1], this.pubkey, session_ctx2, this.tG);
console.log("verify tweaked:", checkpoint);

this.state="waitR2"
return Message_I2;//this message is broadcast offchain
Expand All @@ -246,13 +313,17 @@ export class SCL_Atomic_Initiator{
/********************************************************************************************/
export class SCL_Atomic_Responder{

constructor(curve, pubkey, sk) {
constructor(curve, sk) {

this.signer=new SCL_Musig2(curve);
this.pubkey=pubkey;

this.sk=sk;
this.pubkey=this.signer.IndividualPubKey_array(sk);

this.state="idle";

this.pubKeyDist=0;//the initiator distant public key
this.aggpk = 0;
this.nonceA1=0;
this.nonceA2=0;

Expand All @@ -277,19 +348,25 @@ export class SCL_Atomic_Responder{

RespondInit(Message_I1 )
{
let tx1=Message_I1[0];
let tx2=Message_I1[1];

let nonceA1= Message_I1[2];
let nonceA2= Message_I1[3];

let nonceB1= signer.Nonce_gen(this.sk, this.pubkey, x_aggpk, tx1, extra_in1);
let nonceB2= signer.Nonce_gen(this.sk, this.pubkey, x_aggpk, tx2, extra_in2);

let aggnonce1 = signer.Nonce_agg([nonceA1.toString('hex'), nonceB1[1].toString('hex')]);
let aggnonce2 = signer.Nonce_agg([nonceA2.toString('hex'), nonceB2[1].toString('hex')]);
this.tx1=Message_I1[0];
this.tx2=Message_I1[1];
this.nonceA1= Message_I1[2];
this.nonceA2= Message_I1[3];
this.pubKeyDist=Message_I1[4];

this.aggpk = this.signer.Key_agg([this.pubKeyDist, this.pubkey])[0];
let x_aggpk=this.signer.curve.ForceXonly(this.aggpk);//x-only version for noncegen, allways 32
//anti replay through nonces
let extra_in1=this.signer.curve.Get_Random_privateKey();
let extra_in2=this.signer.curve.Get_Random_privateKey();

let nonceB1= this.signer.Nonce_gen(this.sk, this.pubkey, x_aggpk, this.tx1, extra_in1);
let nonceB2= this.signer.Nonce_gen(this.sk, this.pubkey, x_aggpk, this.tx2, extra_in2);

let aggnonce1 = this.signer.Nonce_agg([this.nonceA1.toString('hex'), nonceB1[1].toString('hex')]);
let aggnonce2 = this.signer.Nonce_agg([this.nonceA2.toString('hex'), nonceB2[1].toString('hex')]);

let Message_R1=[aggnonce1, aggnonce2, nonceB1[1], nonceB2[1], this.pubkey];
let Message_R1=[aggnonce1, aggnonce2, nonceB1[1], nonceB2[1]];

this.state="waitI2";

Expand Down Expand Up @@ -322,42 +399,3 @@ export class SCL_Atomic_Responder{

}


//example of full session with automata
//note that worst case is assumed (Bob read tweak from Alice's signature)
function test_full_atomic_session_automatas(curve){
let signer=new SCL_Musig2(curve);

//generating keypairs
let Initiator=new SCL_Atomic_Initiator(curve, signer.curve.Get_Random_privateKey());
let Responder=new SCL_Atomic_Responder(curve, signer.curve.Get_Random_privateKey());

//the transaction unlocking tokens for Alice and Bob, must be multisigned with Musig2
//Alice want to compute msg1 signed by AB
//Bob wants to compute msg2 signed by AB
const tx1=Buffer.from("Unlock 1strkBTC on Starknet to Alice",'utf-8');
const tx2=Buffer.from("Unlock 1WBTC on Ethereum to Bob",'utf-8');


console.log("Initiator Start session");
let Message_I1=Initiator.InitSession(tx1, tx2); //Initiator sends I1 to responder offchain

console.log("Responder Start session");
let Message_R1=Responder.RespondInit(Message_I1);//Respondeur sends R1 to Initiator offchain

console.log("Initiator Partial Sign and tweak");
let Message_I2=Initiator.PartialSign_Tweaked(Message_R1);//Initiator sends I2 to responder offchain
//At this Point Alice and Bob locks the funds to multisig address on chain 1 and chain 2

console.log("Responder Check and Partial Sign");
let Message_R2=Responder.PartialSign(Message_I2);//Respondeur sends R2 to Initiator offchain

console.log("Initiator Signature Aggregation and Unlock");
let UnlockSigAlice=Initiator.FinalUnlock(Message_R2);//final signature to Unlock chain1 token by Initiator

console.log("Responder Signature Aggregation and Unlock");
let UnlockSigBob=Initiator.FinalUnlock(UnlockSigAlice);//final signature to Unlock chain2 token by Responder

//todo: result is ok if UnlockSigBob is equal to classic multisig

}
64 changes: 64 additions & 0 deletions src/libMPC/test_atomic_swap.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/********************************************************************************************/
/*
/* ___ _ _ ___ _ _ _ _
/* / __|_ __ ___ ___| |_| |_ / __|_ _ _ _ _ __| |_ ___ | | (_) |__
/* \__ \ ' \/ _ \/ _ \ _| ' \ | (__| '_| || | '_ \ _/ _ \ | |__| | '_ \
/* |___/_|_|_\___/\___/\__|_||_| \___|_| \_, | .__/\__\___/ |____|_|_.__/
/* |__/|_|
/*
/* Copyright (C) 2024 - Renaud Dubois - This file is part of SCL (Smooth CryptoLib) project
/* License: This software is licensed under MIT License
/********************************************************************************************/


import{SCL_ecc} from './SCL_ecc.mjs';
import{SCL_Musig2} from './SCL_Musig2.mjs';
import { SCL_Atomic_Initiator, SCL_Atomic_Responder } from './SCL_atomic_swaps.mjs';


//example of full session with automata
//note that worst case is assumed (Bob read tweak from Alice's signature)
function test_full_atomic_session_automatas(curve){
let signer=new SCL_Musig2(curve);

console.log("signer:", signer);
//generating keypairs
let Initiator=new SCL_Atomic_Initiator(curve, signer.curve.Get_Random_privateKey());
let Responder=new SCL_Atomic_Responder(curve, signer.curve.Get_Random_privateKey());

//the transaction unlocking tokens for Alice and Bob, must be multisigned with Musig2
//Alice want to compute msg1 signed by AB
//Bob wants to compute msg2 signed by AB
const tx1=Buffer.from("Unlock 1strkBTC on Starknet to Alice",'utf-8');
const tx2=Buffer.from("Unlock 1WBTC on Ethereum to Bob",'utf-8');


console.log("Initiator Start session");
let Message_I1=Initiator.InitSession(tx1, tx2, Responder.pubkey); //Initiator sends I1 to responder offchain

console.log("Responder Start session");
let Message_R1=Responder.RespondInit(Message_I1);//Respondeur sends R1 to Initiator offchain

console.log("Initiator Partial Sign and tweak");
let Message_I2=Initiator.PartialSign_Tweaked(Message_R1);//Initiator sends I2 to responder offchain
//At this Point Alice and Bob locks the funds to multisig address on chain 1 and chain 2

return 1;
console.log("Responder Check and Partial Sign");
let Message_R2=Responder.PartialSign(Message_I2);//Respondeur sends R2 to Initiator offchain

console.log("Initiator Signature Aggregation and Unlock");
let UnlockSigAlice=Initiator.FinalUnlock(Message_R2);//final signature to Unlock chain1 token by Initiator

console.log("Responder Signature Aggregation and Unlock");
let UnlockSigBob=Initiator.FinalUnlock(UnlockSigAlice);//final signature to Unlock chain2 token by Responder

//todo: result is ok if UnlockSigBob is equal to classic multisig

}


(async () => {
test_full_atomic_session_automatas('secp256k1');

})();

0 comments on commit b70dbc7

Please sign in to comment.