Skip to content

Commit

Permalink
compact tx added
Browse files Browse the repository at this point in the history
response from broadcast handled
full example for mint and transfer added

Signed-off-by: Am.A <[email protected]>
Signed-off-by: Amir m. Aghapour <[email protected]>
  • Loading branch information
Am.A authored and Amir-m-a committed May 14, 2024
1 parent 5a4f6eb commit 85050a3
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 142 deletions.
81 changes: 61 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ The following is a breakdown of sections and classes for this cbdc-module:
- constructor(script_type, pubHex) - ```a new Address Object```
- script_type - {Number} representing the script type of the address
- pubHex - {String} represents valid 32 byte hexadecimal string
- getAddress() - returns {String} ```represented the bech32 encoding version of a publickey e.g. ```
- getAddress() - returns {String} ```represented the bech32 encoding version of a publickey ```
- decodeAddress() - returns {Object} ```with fields script_type (string representing) and pubHex (hex string of public key)```

- static decodeFromAddressString(address) - returns ```{Object} with fields script_type {Number} and pubHex {String} (hexdecimal string of public key)```
Expand All @@ -40,7 +40,9 @@ The following is a breakdown of sections and classes for this cbdc-module:
- secretKeyData - ```{String} a valid 32 byte hexadecimal string```
- static fromPrivateKeyData(secretKeyData)
- secretKeyData - ```{String} a valid 32 byte hexadecimal string```
- toBuffer() - ```returns {Buffer} 32 byte buffer publickey```
- getWitnessCommit(scriptType) - ```returns witness commitment```
- scriptType - ```{string} hex representation of script type ("00" for P2PK)```
- toBuffer() - ```returns {Buffer} 32 byte buffer publickey```

- Secretkey
- contructor(randBytes) - ```returns new SecretKey with randBytes provided as random seed (if provided), otherwise it creates random key from random secrety bytes```
Expand All @@ -54,10 +56,14 @@ The following is a breakdown of sections and classes for this cbdc-module:

## Networking
* Networking
- broadcastTx(port, host, signedTxHex) - ```broadcast a signedTxBuf to a sentinel server at host {host} and port {port} number```
- broadcast(port, host, payloadHex, reqType) - ```Broadcast a payload to a (sentinel/coordinator/shard) and returns a promise that resolves to response```
- port - {number} port number of host
- host - {string} hostname url to send signedTx
- signedTxHex - {string} a valid hexadecimal encoded transaction that has been signed
- host - {string} hostname or url to send payloadHex
- payloadHex - {string} a valid hexadecimal encoded message, could be signed tx or compact tx or any other message
- reqType - {number|null} indicates type of request. can be null for no request type and for others:
- sentinel: 0=execute, 1=validate
- shard (read-only endpoint): 0=UHS, 1=tx
- coordinator doesn't have request type

## Transaction
- Input
Expand All @@ -67,20 +73,9 @@ The following is a breakdown of sections and classes for this cbdc-module:
- witnessProgramCommitment {String} - witness commitment for this input
- value {number} - the number of dollar units this input is worth
- writeInputToBuffer() - ```returns Buffer representation as buffer type```
- getUHSHash() - ```returns {Buffer} Universal Hash Set hash of the input e.g. concatentation of [txid, index, witnessProgramCommitment, value] into bytes```
e.g.
- getUHSHash() - ```returns {Buffer} Universal Hash Set hash of the input i.e. concatenation of [txid, index, witnessProgramCommitment, value] into bytes```
- toString() - ```returns {String} representing valid input```

## Utils
- Utility Methods CBDC module
- sign(secretKey, message) - ```returns signature that signed message {message} with privateKey {privateKey}```
- secretKey - {String} - 32 byte hexadecimal string
- message - {String} - message that is signed
- verify(publicKey, message, signature) ```returns true or false whether the produced signature is validly signed publicKey```
- publicKey - 32 byte hexadecimal string
- message - message to verify was signed
- signature - {String} signature to verify validly signed message against public key message pair

*N.B.:* A hash is used to identify a specfic UTXO within the monetary supply (the UHS ID); it is a concatenation of a txid, a 64-bit index of the UTXO's position in previous tx's outputs, a witnessProgramCommitment, and the 64-bit encoded value of that output

- Output
Expand All @@ -99,11 +94,23 @@ The following is a breakdown of sections and classes for this cbdc-module:
- outputs - Array{Output}
- witnesses - Array of witness object
- toHex() - ```returns {String} hexadecimal string of the unsigned raw transaction```
- getCompactHex(sentinelAttestation) - ```returns {String} compact-tx in hexadecimal format with given sentinel attestations```
- getTxid() - ```returns {String} returns hexadecimal string of transaction id (txid)```
- sign(secretKey) - ```returns {Buffer} signed tx in bytes```
- static txFromHex(rawHex) - ```returns {Transaction} object from the provided rawTx```
- rawHex - valid hexadecimal string represents a valid tx

## Utils
- Utility Methods CBDC module
- sign(secretKey, message) - ```returns signature that signed message {message} with privateKey {privateKey}```
- secretKey - {String} - 32 byte hexadecimal string
- message - {String} - message that is signed
- verify(publicKey, message, signature) ```returns true or false whether the produced signature is validly signed publicKey```
- publicKey - 32 byte hexadecimal string
- message - message to verify was signed
- signature - {String} signature to verify validly signed message against public key message pair


## Special Notes
BigInt is used everywhere throughout this module, that may pose problems in using toJson() methods or serializing for output

Expand Down Expand Up @@ -137,7 +144,7 @@ The following are example code snippets.
```js
let network = require('./networking/broadcast');
const txBuf = new Transaction();
Networking.broadcastTx(5555, '127.0.0.1', Buffer.from(tx.toHex(), 'hex'))
Networking.broadcast(5555, '127.0.0.1', Buffer.from(tx.toHex(), 'hex'))
```
- Transaction
```js
Expand All @@ -153,7 +160,7 @@ The following are example code snippets.
01000000000000009f981e64afc0fc56a0d7b355cd9eba36f3d19507088713b1f73afc5bf301a44e000000000000000070cd87ebaaa0d2d059dccaceeb7f9f823a5791d60b00aef9d9573f1fbf91ca29c800000000000000010000000000000081b095a242974d9f4e98ca18b468b8e644e4168380a035b3d66bc279b36c6510c80000000000000001000000000000006100000000000000003ad8f015f9212f8262248af4cf4cc39907d0215fdde14507f8bc09ad5836bbe901986cc97272bdb7624a824afcef76936b8f945e55d6e8479b95c81298e77b42d5a255e8529fc2f0d90743e7f9997a7159b6121105c7ec9b9252da992f34611f
```

- Constructing, signing and broacasting tx to sentinel
- Constructing, signing and broadcasting tx to sentinel for execution
```js
const secretKey = 'e00b5c3d80899217a22fea87e7337907203df8a1efebd4d2a8773c8f629fff36';
Expand All @@ -163,7 +170,7 @@ The following are example code snippets.
tx.sign(secretKey);
Networking.broadcastTx(5555, '127.0.0.1', Buffer.from(tx.toHex(), 'hex'))
Networking.broadcast(5555, '127.0.0.1', Buffer.from(tx.toHex(), 'hex'))
```

- Signing and Verifying
Expand All @@ -176,6 +183,40 @@ The following are example code snippets.
const sig = utils.sign(secretKey, message);
utils.verify(publicKey, Buffer.from(sha256(message), 'hex'), sig);
```
- Full example for minting and transferring in **2PC** architecture
```js
const user1_sk = new Secretkey();
const user2_sk = new Secretkey();
const user1_pk = new Publickey(user1_sk.secretKeyBuf);
const user2_pk = new Publickey(user2_sk.secretKeyBuf);
const witness1 = user1_pk.getWitnessCommit();
const witness2 = user2_pk.getWitnessCommit();

const sentinel_sk = "SENTINEL_SECRET_KEY_HEX";
const sentinel_pk = Publickey.fromPrivateKeyData(sentinel_sk);

const output1 = new Output(witness1, 50);
const mint_tx = new Transaction([], [output1], []);
const input = new Input(mint_tx.getTxid(), 0, witness1, 50);
const compactMintTx = buffer.from(mint_tx.getCompactHex([]), 'hex');
const sentinelAttestation = buffer.concat([
sentinel_pk,
schnorr.sign(sentinel_sk, buffer.from(Utils.sha256(compactMintTx), 'hex')),
]);
console.log(await Comms.broadcast(SHARD_PORT, SHARD_IP, input.getUHSHash(), 0)); // input UHS doesn't exists, so output will be 0100
console.log(await Comms.broadcast(COORD_PORT, COORD_IP, mint_tx.getCompactHex([sentinel_attest]), null)); // okay mint result will be 0101
console.log(await Comms.broadcast(SHARD_PORT, SHARD_IP, input.getUHSHash(), 0)); // input UHS will exist and output will be 0101

const output22 = new Output(witness2, 20); // pay 20 to user2
const output21 = new Output(witness1, 30); // take 30 back as change
const transferTx = new Transaction([input], [output22, output21], []);
const input21 = new Input(transferTx.getTxid(), 1, witness1, 30);
transferTx.sign(user1_sk.toHex());
console.log(await Comms.broadcast(SHARD_PORT, SHARD_IP, input21.getUHSHash(), 0)); // input21 UHS doesn't exists, so output will be 0100
console.log(await Comms.broadcast(SENTINEL_PORT, SENTINEL_IP, transferTx.toHex(), 0)); // output for successful transfer = 01 00 03 00 00 00 00
console.log(await Comms.broadcast(SHARD_PORT, SHARD_IP, input21.getUHSHash(), 0)); // input21 UHS exists after successful transfer, so output will be 0101
```

## Sample Data

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@mit-dci/opencbdc",
"version": "0.0.2",
"description": "An javascript module for interacting with the opencbdc-tx atomizer and 2pc environments",
"description": "A javascript module for interacting with the opencbdc-tx atomizer and 2pc environments",
"main": "index.js",
"scripts": {
"test": "mocha test/**/*.js",
Expand Down
19 changes: 14 additions & 5 deletions src/crypto/publickey.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
const Secp256k1 = require('@enumatech/secp256k1-js');
const buffer = require('buffer/').Buffer;
const utils = require('./utils');

class PublicKey {

/**
* @param secretKeyData privateKey hex string
*/
constructor(secretKeyData) {
this.secretKeyBuf = buffer.from(secretKeyData, 'hex');
this.publicKey = Secp256k1.generatePublicKeyFromPrivateKeyData(Secp256k1.uint256(secretKeyData, 16)).x;
this.publicKey = Secp256k1.generatePublicKeyFromPrivateKeyData(
Secp256k1.uint256(secretKeyData, 16),
).x;
}

/**
* @param {string} secretKeyBuf
* @returns {Buffer} PublicKey Buffer
* @returns {Buffer} PublicKey Buffer
*/
static fromPrivateKeyData(secretKeyData) {
const pubHex = Secp256k1.generatePublicKeyFromPrivateKeyData(Secp256k1.uint256(secretKeyData, 16)).x;
return buffer.from(pubHex, 'hex');
}


/**
* @param {string} scriptType hex representation of script type (0 for P2PK)
* @returns {Buffer} witness commitment
*/
getWitnessCommit(scriptType = '00') {
return buffer.from(utils.sha256(buffer.from(scriptType + this.publicKey, 'hex')), 'hex');
}

/**
* @returns {Buffer} representing a public key in size x bytes
*/
Expand All @@ -29,4 +39,3 @@ class PublicKey {
}

module.exports = PublicKey;

2 changes: 1 addition & 1 deletion src/crypto/secretkey.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class SecretKey {
* @returns {string} hexadecimal version of private key
*/
toHex() {
return Buffer.toString(this.secretKeyBuf, 'hex');
return this.secretKeyBuf.toString('hex');
}
}

Expand Down
18 changes: 10 additions & 8 deletions src/crypto/utils.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
const schnorr = require('bip-schnorr');
const crypto = require('crypto');

/**
* Return sha256 of input b
* @param {*} b
* @returns
* @param {*} b
* @returns hex SHA256 digest
*/

const sha256 = function a(b){function c(a,b){return a>>>b|a<<32-b;}for(var d,e,f=Math.pow,g=f(2,32),h='length',i='',j=[],k=8*b[h],l=a.h=a.h||[],m=a.k=a.k||[],n=m[h],o={},p=2;64>n;p++)if(!o[p]){for(d=0;313>d;d+=p)o[d]=p;l[n]=f(p,.5)*g|0,m[n++]=f(p,1/3)*g|0;}for(b+='\x80';b[h]%64-56;)b+='\x00';for(d=0;d<b[h];d++){if(e=b.charCodeAt(d),e>>8)return;j[d>>2]|=e<<(3-d)%4*8;}for(j[j[h]]=k/g|0,j[j[h]]=k,e=0;e<j[h];){var q=j.slice(e,e+=16),r=l;for(l=l.slice(0,8),d=0;64>d;d++){var s=q[d-15],t=q[d-2],u=l[0],v=l[4],w=l[7]+(c(v,6)^c(v,11)^c(v,25))+(v&l[5]^~v&l[6])+m[d]+(q[d]=16>d?q[d]:q[d-16]+(c(s,7)^c(s,18)^s>>>3)+q[d-7]+(c(t,17)^c(t,19)^t>>>10)|0),x=(c(u,2)^c(u,13)^c(u,22))+(u&l[1]^u&l[2]^l[1]&l[2]);l=[w+x|0].concat(l),l[4]=l[4]+w|0;}for(d=0;8>d;d++)l[d]=l[d]+r[d]|0;}for(d=0;8>d;d++)for(e=3;e+1;e--){var y=l[d]>>8*e&255;i+=(16>y?0:'')+y.toString(16);}
return i;};

const sha256 = (msg) => {
return crypto.createHash('sha256').update(Buffer.from(msg)).digest('hex');
};

/**
* Returns signature that signed message {message} with privateKey {privateKey}
Expand All @@ -34,7 +36,7 @@ const verify = (publicKey, message, signature) => {
try {
schnorr.verify(pubkeyBuf, messageBuf, sigBuf);
} catch (error) {
console.log('Error msg: ', error);
console.log('Error msg: ', error);
return false;
}
return true;
Expand All @@ -43,5 +45,5 @@ const verify = (publicKey, message, signature) => {
module.exports = {
sign,
sha256,
verify
};
verify,
};
80 changes: 49 additions & 31 deletions src/networking/broadcast.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,57 @@ const net = require('net');
const buffer = require('buffer/').Buffer;

const Networking = {
// Broadcast a rawTxBuffer to a sentinel at specified host {hostURL} and port {port} number
// make sure to include the size of the rawTxBuffer before sending to the sentinel
// reqType indicates execute transaction (0) or validate transaction (1)
broadcastTx: (port, host, signedTxHex, reqType = 0) => {

const client = new net.Socket();

client.connect(port, host, () => {
console.log('Connected');
const requestId = Math.round(Math.random() * 10000000);
const reqIdBuf = buffer.alloc(8);
reqIdBuf.writeBigUInt64LE(BigInt(requestId));
const reqTypeBuf = buffer.alloc(1);
/**
* Broadcast a payload to a (sentinel/coordinator/shard)
* @param {number} port tcp port of destination
* @param {string} host ip address or hostname of destination
* @param {string} payloadHex hex representation of payload, could be tx or compact tx or any other message
* @param {(number|null)} reqType indicates type of request. can be null for no request type.
* - for sentinel 0=execute, 1=validate
* - for shard 0=UHS, 1=tx
* - coordinator doesn't have request type
* @return {Promise<Buffer>} response from destination
*/
broadcast: (port, host, payloadHex, reqType = 0) => {
const requestId = Math.round(Math.random() * Math.pow(2, 64));
const reqIdBuf = buffer.alloc(8);
reqIdBuf.writeBigUInt64LE(BigInt(requestId));
let reqTypeBuf;
if (reqType === null) {
// no request type in packet (when sending to coordinator)
reqTypeBuf = buffer.alloc(0);
} else {
reqTypeBuf = buffer.alloc(1);
reqTypeBuf.writeUInt8(reqType);
const sizePacket = buffer.alloc(8);
const signedTxBuffer = buffer.from(signedTxHex, 'hex');
sizePacket.writeBigUInt64LE(BigInt(signedTxBuffer.length + reqIdBuf.length + reqTypeBuf.length));
const finalPacket = buffer.concat([sizePacket, reqIdBuf, reqTypeBuf, signedTxBuffer]);
client.write(finalPacket);
});

client.on('data', (data) => {
console.log('Received: ' + data.toString('hex'));
// TODO: read response completely (based on packet length) to prevent killing prematurely
client.destroy(); // kill client after server's response
});

client.on('close', () => {
console.log('Connection closed');
});
}
const payloadBuffer = buffer.from(payloadHex, 'hex');
const sizePacket = buffer.alloc(8);
sizePacket.writeBigUInt64LE(BigInt(payloadBuffer.length + reqIdBuf.length + reqTypeBuf.length));
const finalPacket = buffer.concat([sizePacket, reqIdBuf, reqTypeBuf, payloadBuffer]);

client.on('error', (err) => {
console.error(err);
return new Promise((resolve, reject) => {
const client = new net.Socket(); // TODO: use single socket per class instance
client.setTimeout(5000, client.kill); // 5s timeout
client.on('error', reject);
client.on('timeout', reject);
client.on('close', reject);
client.connect(port, host, () => {
let packetLength;
let receivedData = buffer.alloc(0);
client.on('data', (chunk) => {
receivedData = Buffer.concat([receivedData, chunk]);
if (packetLength === undefined && receivedData.length >= 8) {
packetLength = receivedData.readBigUInt64LE();
receivedData = receivedData.subarray(8);
}
if (packetLength !== undefined && receivedData.length >= packetLength) {
resolve(receivedData.subarray(8)); // ignore request id
client.destroy(); // kill client after server's complete response
}
client.read(); // poll for any buffered data
});
client.write(finalPacket);
});
});
},
};
Expand Down
1 change: 1 addition & 0 deletions src/transaction/input.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const buffer = require('buffer/').Buffer;
const crypto = require('crypto');

/*
* Input class represents the 'input' abstraction in digital currency transaction
Expand Down
Loading

0 comments on commit 85050a3

Please sign in to comment.