Skip to content

Commit c728c2c

Browse files
toward Atomic Swaps + reorganization
1 parent 59a34e5 commit c728c2c

File tree

3 files changed

+307
-29
lines changed

3 files changed

+307
-29
lines changed

src/libMPC/SCL_Musig2.mjs

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@
1010
/* License: This software is licensed under MIT License
1111
/********************************************************************************************/
1212

13-
import { createHash } from 'crypto';
1413
import { ed25519 } from '@noble/curves/ed25519';
15-
import{reverse, bytes_xor, int_from_bytes, int_to_bytes} from "./common.mjs";
16-
import { tagged_hashBTC } from './bip327.mjs';
14+
import{reverse, bytes_xor, int_from_bytes, int_to_bytes, tagged_hashBTC, taghash_rfc8032} from "./common.mjs";
15+
1716
import { secp256k1 } from '@noble/curves/secp256k1';
1817
import { etc, utils, getPublicKey } from '@noble/secp256k1';
1918
import{SCL_ecc} from './SCL_ecc.mjs';
2019
import { randomBytes } from 'crypto'; // Use Node.js's crypto module
2120

21+
22+
/********************************************************************************************/
23+
/* CLASS MUSIG2 */
24+
/********************************************************************************************/
2225
// Utility to handle different curves
2326
export class SCL_Musig2
2427
{
@@ -179,7 +182,6 @@ export class SCL_Musig2
179182

180183
// Compute the tagged hash with 'MuSig/nonce' as the tag
181184
const hash = this.TagHash('MuSig/nonce', buf);
182-
183185
// Return the result as a BigInt
184186
return hash;
185187
}
@@ -462,32 +464,12 @@ Psign(secnonce, sk, session_ctx){
462464
}
463465

464466

465-
}//end of class Musig2
466-
467-
// Function to compute sha256 hash
468-
export function sha512(data) {
469-
return createHash('sha512').update(data).digest();
470-
}
471-
472-
473-
//look at endianness error
474-
export function taghash_rfc8032(tag, message){
475-
// Convert the tag to a sha256 hash (as bytes)
476-
const U8tag = Buffer.from(tag, 'utf-8');
477-
478-
// Concatenate (encodePacked) tagHash, tagHash, and the message
479-
let encoded = Buffer.concat([U8tag, message]);
480-
481-
// Compute final sha256 hash
482-
let finalHash = sha512(encoded);
483-
//swap then reduce mod q (damned endians)
484-
finalHash=finalHash.reverse();
485-
486-
finalHash= int_from_bytes(finalHash) % ed25519.CURVE.n;
467+
}
468+
/********************************************************************************************/
469+
/* END OF CLASS MUSIG2 */
470+
/********************************************************************************************/
487471

488472

489-
return int_to_bytes(finalHash,32);
490-
}
491473

492474

493475

src/libMPC/SCL_atomic_swaps.mjs

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
/********************************************************************************************/
2+
/*
3+
/* ___ _ _ ___ _ _ _ _
4+
/* / __|_ __ ___ ___| |_| |_ / __|_ _ _ _ _ __| |_ ___ | | (_) |__
5+
/* \__ \ ' \/ _ \/ _ \ _| ' \ | (__| '_| || | '_ \ _/ _ \ | |__| | '_ \
6+
/* |___/_|_|_\___/\___/\__|_||_| \___|_| \_, | .__/\__\___/ |____|_|_.__/
7+
/* |__/|_|
8+
/*
9+
/* Copyright (C) 2024 - Renaud Dubois - This file is part of SCL (Smooth CryptoLib) project
10+
/* License: This software is licensed under MIT License
11+
/********************************************************************************************/
12+
import{nonce_gen_internal, nonce_agg, key_agg, IndividualPubKey, psign, partial_sig_verify_internal} from './bip327.mjs'
13+
14+
import { utils, getPublicKey } from '@noble/secp256k1';
15+
16+
17+
import{SCL_ecc} from './SCL_ecc.mjs';
18+
import{SCL_Musig2} from './SCL_Musig2.mjs';
19+
20+
import { randomBytes } from 'crypto'; // Use Node.js's crypto module
21+
22+
23+
24+
//the Alice adaptator signature
25+
//sk_A is Alice secret Key
26+
//rA is the secnonce
27+
//R is the public agg nonce
28+
//Pub_AB is the Musig2 agreed multisig key between Alice and Bob
29+
//Pub_A is Alice public key
30+
31+
export function psign_adapt(psig, t){
32+
33+
sprime=(int_from_bytes(psig)+int_from_bytes(t)) % secp256k1.CURVE.n;
34+
35+
return sprime;
36+
}
37+
38+
//check that a tweaked partially signature is valid
39+
export function partial_adaptatorsig_verify_internal(psig, pubnonce, pk, session_ctx, T){
40+
41+
return true;
42+
}
43+
44+
export function atomic_check(tG, psA1, psA2, psB1, psB2, QA, R, msg1, msg2){
45+
46+
return true;
47+
}
48+
49+
50+
export function get_tweak_from_sigs(sAp, sB, sAB)
51+
{
52+
const sABp=partial_sig_agg([sAp, sB]);
53+
t=(sABp-sAB)% secp256k1.CURVE.n;
54+
return t;
55+
}
56+
57+
//the function takes as input an adaptator signature, its tweak t, and a valid signature, and returns the Musig2 corresponding signature
58+
export function sign_untweak(t, psigA_adapt, psigB){
59+
const sABp=partial_sig_agg([psigA_adapt, psigB]);
60+
61+
const sAB= (sABp - t)% secp256k1.CURVE.n;
62+
63+
return sAB;
64+
}
65+
66+
export function atomic_example(){
67+
68+
// Alice and Bob private keys
69+
const skA = utils.randomPrivateKey();
70+
console.log("sk", skA);
71+
const skB = utils.randomPrivateKey();
72+
73+
//Alice and Bob public keys
74+
const QA= IndividualPubKey(skA);
75+
const QB= IndividualPubKey(skB);
76+
77+
const pubkeys=[QA, QB];
78+
79+
const msg1=Buffer.from("Unlock 1strkBTC on Starknet to Alice",'utf-8');
80+
console.log("msg1=",msg1);
81+
const msg2=Buffer.from("Unlock 1WBTC on Ethereum to Bob",'utf-8');
82+
83+
84+
//key aggregation
85+
const QAB=key_agg([QA, QB])[0];
86+
console.log("QAB=",QAB);
87+
88+
//nonce generation
89+
90+
const nonceA1=nonce_gen_internal(utils.randomPrivateKey(), skA, QA, QAB, msg1, Buffer.from(""));//alice generates its nonce
91+
const nonceA2=nonce_gen_internal(utils.randomPrivateKey(), skA, QA, QAB, msg2, Buffer.from(""));//alice generates its nonce
92+
93+
const nonceB1=nonce_gen_internal(utils.randomPrivateKey(), skB, QB, QAB, msg1, Buffer.from(""));//alice generates its nonce
94+
const nonceB2=nonce_gen_internal(utils.randomPrivateKey(), skB, QB, QAB, msg2, Buffer.from(""));//alice generates its nonce
95+
96+
97+
//alice and Bob construct common nonce from pubnonces
98+
const R1=nonce_agg([nonceA1[1], nonceB1[1]]);
99+
const R2=nonce_agg([nonceA2[1], nonceB2[1]]);
100+
101+
session_ctx1=[R1, pubkeys, [], [], msg1];
102+
session_ctx2=[R1, pubkeys, [], [], msg2];
103+
104+
105+
//Alice locks one BTC on Ethereum, using the corresponding Musig2 adress, it is unlocked with msg2 multisig or timelock expiration
106+
//bob locks one BTC on starknet, using the corresponding Musig2 adress, it is unlocked with msg1 multisig or timelock expiration
107+
108+
//alice generates a secret adaptator tweak and publish offchain the value tG
109+
const t = utils.randomPrivateKey();
110+
111+
//alice generates the secret adaptator signatures sA'1 and sA'2 for both message and broadcast them offchain
112+
const psigA1=psign(nonceA1[0], skA, session_ctx1)
113+
const sAp1=psign_adapt(psigA1, t);
114+
const psigA2=psign(nonceA2[0], skA, session_ctx1)
115+
const sAp2=psign_adapt(psigA2, t);
116+
117+
//bob check the compliance, then broadcast offchain signature of message 1 sb1
118+
psigB1=psign(nonceB1, skB, session_ctx1);
119+
120+
//Alice unlocks its strkBTC, using sb1, thus revealing the tweak to Bob
121+
sAB1=partial_sig_agg([psigA1, psigB1], session_ctx1);
122+
123+
//Bob reads onchain 1 the value of t, then compute the value of SAB2, unlocking its token
124+
const rec_t=get_tweak_from_sigs(sAB1, sAp1, psigB1);
125+
const sAB2=sign_untweak( psigA2);
126+
127+
}
128+
129+
export class SCL_Atomic_Swap
130+
{
131+
constructor(curve) {
132+
this.signer=new SCL_Musig2(curve);
133+
134+
this.curve=signer.curve;
135+
}
136+
137+
Psign_adapt(psig, t){
138+
139+
140+
let sprime=(int_from_bytes(psig)+t ) % this.curve.order;
141+
let G= this.curve.GetBase(t);
142+
143+
return [sprime, G.multiply(t)];
144+
}
145+
146+
Untweak(t, psigA_adapt, psigB){
147+
const sABp=partial_sig_agg([psigA_adapt, psigB]);
148+
149+
const sAB= (sABp - t)% secp256k1.CURVE.n;
150+
151+
return sAB;
152+
}
153+
154+
}
155+
156+
157+
function test_full_atomic_session(curve){
158+
const swapper= new SCL_Atomic_Swap(curve);
159+
const signer = swapper.signer;
160+
161+
console.log("/*************************** ");
162+
console.log("Test full Atomic session on curve", Curve);
163+
164+
console.log(" -Generate random keys");
165+
166+
const sk1=signer.curve.Get_Random_privateKey();//this provides a 32 bytes array
167+
const sk2=signer.curve.Get_Random_privateKey();
168+
169+
console.log("sk1=",sk1 );
170+
console.log("sk2=",sk2 );
171+
let seckeys=[sk1, sk2];
172+
173+
const pubK1=signer.IndividualPubKey_array(sk1);
174+
const pubK2=signer.IndividualPubKey_array(sk2);
175+
176+
console.log("pubK1=",pubK1 );
177+
console.log("pubK2=",pubK2 );
178+
179+
const pubkeys=[pubK1, pubK2];
180+
181+
let aggpk = signer.Key_agg(pubkeys)[0];//here aggpk is a 32 or 33 bytes compressed public key
182+
let x_aggpk=signer.curve.ForceXonly(aggpk);//x-only version for noncegen, allways 32
183+
184+
console.log("Aggregated Pubkey:", aggpk);
185+
186+
const msg1=Buffer.from("Unlock 1strkBTC on Starknet to Alice",'utf-8');
187+
console.log("msg1=",msg1);
188+
const msg2=Buffer.from("Unlock 1WBTC on Ethereum to Bob",'utf-8');
189+
console.log("msg2=",msg2);
190+
191+
192+
//nonce generation
193+
console.log(" -Generate random Nonces with commitment", aggpk);
194+
//diversification chain
195+
const extra_in1= Buffer.from(randomBytes(32));
196+
const extra_in2= Buffer.from(randomBytes(32));
197+
198+
199+
let nonceA1= signer.Nonce_gen(seckeys[0], pubkeys[0], x_aggpk, msg1, extra_in1);
200+
let nonceB1= signer.Nonce_gen(seckeys[1], pubkeys[1], x_aggpk, msg2, extra_in1);
201+
202+
let nonceA2= signer.Nonce_gen(seckeys[0], pubkeys[0], x_aggpk, msg1, extra_in2);
203+
let nonceB2= signer.Nonce_gen(seckeys[1], pubkeys[1], x_aggpk, msg2, extra_in2);
204+
205+
206+
//aggregation of public nonces
207+
let aggnonce1 = signer.Nonce_agg([nonceA1[1].toString('hex'), nonceB1[1].toString('hex')]);
208+
let aggnonce2 = signer.Nonce_agg([nonceA2[1].toString('hex'), nonceB2[1].toString('hex')]);
209+
210+
211+
const session_ctx1=[aggnonce1, pubkeys, [], [], msg];
212+
const session_ctx2=[aggnonce2, pubkeys, [], [], msg];
213+
214+
let pA1=signer.Psign(nonceA1[0], seckeys[0], session_ctx1);
215+
let pA2=signer.Psign(nonceA2[0], seckeys[0], session_ctx2);
216+
217+
218+
let pB1=signer.Psign(nonceB1[0], seckeys[0], session_ctx1);
219+
let pB2=signer.Psign(nonceB2[0], seckeys[0], session_ctx2);
220+
221+
//Alice tweaks signatures
222+
let t=int_from_bytes(signer.curve.Get_Random_privateKey());
223+
let atomic_ctx1=swapper.Psign_adapt(pA1, t);
224+
let atomic_ctx2=swapper.Psign_adapt(pA2, t);
225+
226+
//todo:Bob checks compliance of tG, pA2p, pA1p, msg1, msg2
227+
228+
229+
//Bob compute sAB', then substract to obtain sAB
230+
let sAB1=swapper.Untweak(t,atomic_ctx1 );
231+
232+
//
233+
234+
235+
236+
237+
console.log("p1=",p1);
238+
239+
}

src/libMPC/common.mjs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
/* License: This software is licensed under MIT License
1111
/********************************************************************************************/
1212

13+
import { createHash } from 'crypto';
14+
15+
1316
export function bytes_xor(a,b){
1417
if (a.length !== b.length) {
1518
throw new Error('Byte arrays must be of the same length');
@@ -21,6 +24,9 @@ export function bytes_xor(a,b){
2124
return c;
2225
}
2326

27+
/********************************************************************************************/
28+
/* ENCODINGS */
29+
/********************************************************************************************/
2430
//convert bytes to bigInt
2531
export function int_from_bytes(bytes){
2632
return BigInt('0x' + Buffer.from(bytes).toString('hex'));
@@ -63,4 +69,55 @@ export function int_to_bytes(value, byteLength){
6369
//reverse the byte endianness of a buffer (mirroring from/to lsb/msb)
6470
export function reverse(msb){
6571
return Buffer.from([...msb].reverse());
66-
}
72+
}
73+
74+
/********************************************************************************************/
75+
/* HASHES */
76+
/********************************************************************************************/
77+
// Tagged hash function compliant with BIP327
78+
// tag: str
79+
// message: bytes
80+
export function tagged_hashBTC(tag, message) {
81+
// Convert the tag to a sha256 hash (as bytes)
82+
const tagHash = sha256(Buffer.from(tag, 'utf-8'));
83+
84+
// Concatenate (encodePacked) tagHash, tagHash, and the message
85+
const encoded = Buffer.concat([tagHash, tagHash, message]);
86+
87+
// Compute final sha256 hash
88+
const finalHash = sha256(encoded);
89+
90+
return finalHash;
91+
}
92+
93+
94+
//look at endianness error
95+
export function taghash_rfc8032(tag, message){
96+
// Convert the tag to a sha256 hash (as bytes)
97+
const U8tag = Buffer.from(tag, 'utf-8');
98+
99+
// Concatenate (encodePacked) tagHash, tagHash, and the message
100+
let encoded = Buffer.concat([U8tag, message]);
101+
102+
// Compute final sha256 hash
103+
let finalHash = sha512(encoded);
104+
//swap then reduce mod q (damned endians)
105+
finalHash=finalHash.reverse();
106+
107+
finalHash= int_from_bytes(finalHash) % BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989');
108+
109+
110+
return int_to_bytes(finalHash,32);
111+
}
112+
113+
114+
// Function to compute sha256 hash
115+
export function sha256(data) {
116+
return createHash('sha256').update(data).digest();
117+
}
118+
119+
120+
// Function to compute sha256 hash
121+
export function sha512(data) {
122+
return createHash('sha512').update(data).digest();
123+
}

0 commit comments

Comments
 (0)