How can you sign hyperledger-sawtooth transactions using metamask keys? - hyperledger-sawtooth

Hyperledger sawtooth uses secp256k1 ECDSA to sign transactions:
https://sawtooth.hyperledger.org/docs/core/releases/1.2.5/_autogen/txn_submit_tutorial.html?highlight=transaction%20sign
And aparently ethereum uses the same type of signature:
https://hackernoon.com/a-closer-look-at-ethereum-signatures-5784c14abecc
Thus, it would seem that because Metamask is used with Ethereum it would also work with sawtooth. However, I haven't found examples of this, and although I've tried signing transactions with web3.js and ethers.js with Metamask those signatures get rejected by Sawtooth.

It's possible, this is an example I made using web3:0.20.7:
https://github.com/le99/sawtooth-with-metamask-signatures/blob/master/src/App.js
The important function is onClick()
import './App.css';
import React, { useState } from 'react';
var ethUtil = require('ethereumjs-util')
const secp256k1 = require('secp256k1')
const CryptoJS = require('crypto-js');
const axios = require('axios').default;
const cbor = require('cbor')
const Web3 = require('web3');
//https://github.com/ethereum/web3.js/blob/0.20.7/DOCUMENTATION.md
// let web3 = new Web3(Web3.givenProvider || "ws://localhost:8545");
let web3;
if (typeof window.web3 !== 'undefined') {
web3 = new Web3(window.web3.currentProvider);
} else {
// set the provider you want from Web3.providers
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}
const hash = (x) =>
CryptoJS.SHA512(x).toString(CryptoJS.enc.Hex)
// https://stackoverflow.com/questions/33914764/how-to-read-a-binary-file-with-filereader-in-order-to-hash-it-with-sha-256-in-cr
function arrayBufferToWordArray(ab) {
var i8a = new Uint8Array(ab);
var a = [];
for (var i = 0; i < i8a.length; i += 4) {
a.push(i8a[i] << 24 | i8a[i + 1] << 16 | i8a[i + 2] << 8 | i8a[i + 3]);
}
return CryptoJS.lib.WordArray.create(a, i8a.length);
}
async function onClick(){
const ethereum = window.ethereum;
var from = web3.eth.accounts[0]
// var msgHash = ethUtil.keccak256(Buffer.from('An amazing message, for use with MetaMask!'))
var msgHash = Buffer.from('8144a6fa26be252b86456491fbcd43c1de7e022241845ffea1c3df066f7cfede', 'hex');
console.log(from);
let signature1 = await new Promise((resolve, reject)=>{
web3.eth.sign(from, msgHash, function (err, result) {
if (err) return reject(err)
return resolve(result)
})
});
const rpk3 = secp256k1.ecdsaRecover(Uint8Array.from(Buffer.from(signature1.slice(2, -2), 'hex')), parseInt(signature1.slice(-2), 16) - 27, Uint8Array.from(msgHash));
let publicKey = Buffer.from(rpk3, 'hex').toString('hex')
console.log(msgHash.toString('hex'));
console.log(signature1);
console.log(publicKey);
console.log();
const INT_KEY_FAMILY = 'intkey'
const INT_KEY_NAMESPACE = hash(INT_KEY_FAMILY).substring(0, 6)
const address = INT_KEY_NAMESPACE + hash('foo').slice(-64)
console.log('address:',address);
const payload = {
Verb: 'set',
Name: 'foo',
Value: 41
}
console.log('public:', publicKey);
const payloadBytes = cbor.encode(payload)
const protobuf = require('sawtooth-sdk/protobuf')
const transactionHeaderBytes = protobuf.TransactionHeader.encode({
familyName: 'intkey',
familyVersion: '1.0',
inputs: [address],
outputs: [address],
signerPublicKey: publicKey,
// In this example, we're signing the batch with the same private key,
// but the batch can be signed by another party, in which case, the
// public key will need to be associated with that key.
batcherPublicKey: publicKey,
// In this example, there are no dependencies. This list should include
// an previous transaction header signatures that must be applied for
// this transaction to successfully commit.
// For example,
// dependencies: ['540a6803971d1880ec73a96cb97815a95d374cbad5d865925e5aa0432fcf1931539afe10310c122c5eaae15df61236079abbf4f258889359c4d175516934484a'],
dependencies: [],
payloadSha512: CryptoJS.SHA512(arrayBufferToWordArray(payloadBytes)).toString(CryptoJS.enc.Hex),
nonce:"hey4"
}).finish()
let sss=CryptoJS.SHA256(arrayBufferToWordArray(transactionHeaderBytes)).toString(CryptoJS.enc.Hex);
let dataHash=Uint8Array.from(Buffer.from(sss, 'hex'));
let signature = await new Promise((resolve, reject)=>{
web3.eth.sign(from, dataHash, function (err, result) {
if (err) return reject(err)
return resolve(result)
})
});
signature = signature.slice(2, -2)
console.log('sha1:', CryptoJS.SHA512(arrayBufferToWordArray(transactionHeaderBytes)).toString(CryptoJS.enc.Hex))
console.log('signature1:', signature)
const transaction = protobuf.Transaction.create({
header: transactionHeaderBytes,
headerSignature: signature,
payload: payloadBytes
})
//--------------------------------------
//Optional
//If sending to sign outside
const txnListBytes = protobuf.TransactionList.encode({transactions:[
transaction
]}).finish()
//const txnBytes2 = transaction.finish()
let transactions = protobuf.TransactionList.decode(txnListBytes).transactions;
//----------------------------------------
//transactions = [transaction]
const batchHeaderBytes = protobuf.BatchHeader.encode({
signerPublicKey: publicKey,
transactionIds: transactions.map((txn) => txn.headerSignature),
}).finish()
//
sss=CryptoJS.SHA256(arrayBufferToWordArray(batchHeaderBytes)).toString(CryptoJS.enc.Hex);
dataHash=Uint8Array.from(Buffer.from(sss, 'hex'));
signature = await new Promise((resolve, reject)=>{
web3.eth.sign(from, dataHash, function (err, result) {
if (err) return reject(err)
return resolve(result)
})
});
signature = signature.slice(2, -2)
const batch = protobuf.Batch.create({
header: batchHeaderBytes,
headerSignature: signature,
transactions: transactions
})
const batchListBytes = protobuf.BatchList.encode({
batches: [batch]
}).finish()
console.log(Buffer.from(batchListBytes).toString('hex'));
console.log('batchListBytes has the batch bytes that ca be sent to sawtooth')
// axios.post(`${HOST}/batches`, batchListBytes, {
// headers: {'Content-Type': 'application/octet-stream'}
// })
// .then((response) => {
// console.log(response.data);
// })
// .catch((err)=>{
// console.log(err);
// });
}
The example is based on:
https://sawtooth.hyperledger.org/docs/core/releases/1.2.6/_autogen/sdk_submit_tutorial_js.html
There is a lot of low level stuff, hyperledger and Metamask represent signatures slightly differently. Also most libraries for Metamask automatically wrap the data (https://web3js.readthedocs.io/en/v1.2.11/web3-eth-accounts.html#sign), they then hash it using keccak256, and that hash is what is finnally signed with secp256k1, which is not what you need for Sawtooth.
An example where no wraping or intermediaries are used to sign is: https://github.com/danfinlay/js-eth-personal-sign-examples/blob/master/index.js

Related

VRFCoordinatorV2Mock's fulfillRandomWords not being called in hardhat local deployment

I'm building a smart contract that generates 3 random numbers using Chainlink VRF. My contract (SlotMachine.sol) implements the VRFConsumerBaseV2 and the constructor has 2 parameters: _subscriptionId and _vrfCoordinator:
constructor(
uint64 _subscriptionId,
address _vrfCoordinator
) payable VRFConsumerBaseV2(_vrfCoordinator)
I have a play() function which calls the requestRandonWords through the vrfCoordinator and I overrided the fulfillRandomWord function to use the generated random words.
I want to do 2 things: 1) create the unit tests in slotmachine.js and 2) test my contract in the hardhat network (chainId = 31337) through deploy.js
For this I'm using the VRFCoordinatorV2Mock which helps me mock an oracle's behavior (as I understand).
My unit tests in slotmachine.js are working well. The fulfillRandomWords is called and then my tests are passing. However when I add a similar logic to the deploy.js file (deploy the mock contract and then the slot machine contract) the fulfillRandomWords is not being called when I call the play() function (which has the requestRandomWords inside it) from my deployed contract.
slotmachine.js
const { ethers } = require("hardhat");
const { expect } = require("chai");
const { BigNumber } = require("ethers");
const provider = ethers.getDefaultProvider();
describe("Decentralized Slot Machine", async function () {
let myContract;
let hardhatVrfCoordinatorV2Mock;
describe("Testing Decentralized Slot Machine", function () {
//1. Contract deployment
it("Should deploy Slot Machine Contract", async function () {
const SlotMachine = await ethers.getContractFactory("SlotMachine");
let vrfCoordinatorV2Mock = await ethers.getContractFactory(
"VRFCoordinatorV2Mock"
);
hardhatVrfCoordinatorV2Mock = await vrfCoordinatorV2Mock.deploy(0, 0);
await hardhatVrfCoordinatorV2Mock.createSubscription();
await hardhatVrfCoordinatorV2Mock.fundSubscription(
1,
ethers.utils.parseEther("7")
);
myContract = await SlotMachine.deploy(
1,
hardhatVrfCoordinatorV2Mock.address,
{
value: ethers.utils.parseEther("100"),
}
);
await hardhatVrfCoordinatorV2Mock.addConsumer(1, myContract.address);
});
//2. Play
describe("First Play - First Player", function () {
it("Contract should receive random numbers", async () => {
const [account1, account2] = await ethers.getSigners();
let tx = await myContract.play(ethers.constants.AddressZero, {
value: ethers.utils.parseEther("1"),
});
let { events } = await tx.wait();
let [reqId] = events.filter((x) => x.event === "RequestedRandomness")[0]
.args;
await expect(
hardhatVrfCoordinatorV2Mock.fulfillRandomWords(
reqId,
myContract.address
)
).to.emit(myContract, "ReceivedRandomness");
let round = await myContract.rounds(reqId);
expect(round.userAddress).to.be.equal(account1.address);
expect(round.number1).to.be.equal(1);
expect(round.number2).to.be.equal(9);
expect(round.number3).to.be.equal(6);
});
});
});
});
deploy.js
const { ethers } = require("hardhat");
const localChainId = "31337";
module.exports = async ({ getNamedAccounts, deployments, getChainId }) => {
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();
const chainId = await getChainId();
await deploy("VRFCoordinatorV2Mock", {
from: deployer,
args: [0, 0],
log: true,
waitConfirmations: 5,
});
const hardhatVrfCoordinatorV2Mock = await ethers.getContract(
"VRFCoordinatorV2Mock",
deployer
);
await hardhatVrfCoordinatorV2Mock.createSubscription();
await hardhatVrfCoordinatorV2Mock.fundSubscription(
1,
ethers.utils.parseEther("7")
);
const myContract = await deploy("SlotMachine", {
from: deployer,
args: [1, hardhatVrfCoordinatorV2Mock.address],
log: true,
waitConfirmations: 5,
});
await hardhatVrfCoordinatorV2Mock.addConsumer(1, myContract.address);
console.log("Contract address: ", myContract.address);
};
module.exports.tags = ["SlotMachine"];
Question
Can I deploy my contract in the hardhat network with the expected behavior of the VRFCoordinatorV2Mock which is to call automatically fulfillRandomWords? If so, how can I do this? What do I have to change in my code? If the answer is no, is there another alternative than deploying to a testnet?

Unhandled Rejection (Error): call revert exception problem

Error Image
Solution found online
const submitHandler = async (e) => {
e.preventDefault();
// handleMint();
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const erc20 = new ethers.Contract("0xbe3ccbfc866b16bd983b795b6ffa8c0f98b2540e", Abi, signer);
// const tokenName = await erc20.name();
const { data } = await axios.get(`/api/products/`);
// console.log("my data: ",data);
await erc20.createNFT(""); //change krna h
let mintId= await erc20.TokenID();
let tokenId = parseInt(mintId._hex, 16);
console.log(tokenId);
// const check = await erc20.ownerOf(tokenId);
// console.log(check);
try{
await erc20.listNFT(tokenId, price); //added for sale
}catch(err){
console.log(err);
}
const add3= erc20.ownerOf(tokenId);
console.log(add3);
dispatch(createProduct(name, tokenId, price, warranty, description, image, countInStock));
};
I think there is problem with the minting is there any solutions? I am newbie to this field. Thank you.strong text

× Unhandled Rejection (Error): invalid BigNumber value (argument="value", value=[11,15,17,18,19,21], code=INVALID_ARGUMENT, version=bignumber/5.6.2)

I'm attempting to rework this function which currently cycles through the call of the walletOfOwner to return all owned tokenIDs, then stages each for a separate transaction.
I cannot seem to get the array pulled in a way that is accepted as it can be on etherscan and looking for some assistance.
Original Code
async function stakeall() {
var rawnfts = await vaultcontract.methods.tokensOfOwner(account).call();
const arraynft = Array.from(rawnfts.map(Number));
const tokenid = arraynft.filter(Number);
await Web3Alc.eth.getMaxPriorityFeePerGas().then((tip) => {
Web3Alc.eth.getBlock('pending').then((block) => {
var baseFee = Number(block.baseFeePerGas);
var maxPriority = Number(tip);
var maxFee = maxPriority + baseFee;
tokenid.forEach(async (id) => {
await vaultcontract.methods.stake([id])
.send({
from: account,
maxFeePerGas: maxFee,
maxPriorityFeePerGas: maxPriority
})
})
});
})
}
Reworked
async function stakeall() {
var rawnfts = await contract.methods.walletOfOwner(account).call();
const arraynft = Array.from(rawnfts.map(Number));
const tokenids = arraynft
await Web3Alc.eth.getMaxPriorityFeePerGas().then((tip) => {
Web3Alc.eth.getBlock('pending').then((block) => {
var baseFee = Number(block.baseFeePerGas);
var maxPriority = Number(tip);
var maxFee = maxPriority + baseFee;
vaultcontract.methods.stake([tokenids])
.send({
from: account,
maxFeePerGas: maxFee,
maxPriorityFeePerGas: maxPriority
})
});
})
}
I can perform this function successfully with the same array on etherscan, but am not too sure where to go from here, everything I've found states it's only possible to send one tokenId at a time, however, I can call the same function on etherscan successfully using their ui

How to create a unprotected api in Json server jwt auth

I am using json server with jwt auth to create a fake api.After successful registration or login i am receiving one token and using this token I can access the database.json.Without registation or login i can't access database.json file,but I want to access one api with out verifying my self to show all product to guest user.how it is possible.
I want a api like localhost:8000/products so I can access it without authorization,how to archive this in this js file.
I am using this code from git.
https://github.com/techiediaries/fake-api-jwt-json-server/blob/master/server.js
const fs = require('fs')
const bodyParser = require('body-parser')
const jsonServer = require('json-server')
const jwt = require('jsonwebtoken')
const server = jsonServer.create()
const router = jsonServer.router('./database.json')
const userdb = JSON.parse(fs.readFileSync('./users.json', 'UTF-8'))
server.use(bodyParser.urlencoded({extended: true}))
server.use(bodyParser.json())
server.use(jsonServer.defaults());
const SECRET_KEY = '123456789'
const expiresIn = '1h'
// Create a token from a payload
function createToken(payload){
return jwt.sign(payload, SECRET_KEY, {expiresIn})
}
// Verify the token
function verifyToken(token){
return jwt.verify(token, SECRET_KEY, (err, decode) => decode !== undefined ? decode : err)
}
// Check if the user exists in database
function isAuthenticated({email, password}){
return userdb.users.findIndex(user => user.email === email && user.password === password) !== -1
}
// Register New User
server.post('/auth/register', (req, res) => {
console.log("register endpoint called; request body:");
console.log(req.body);
const {email, password} = req.body;
if(isAuthenticated({email, password}) === true) {
const status = 401;
const message = 'Email and Password already exist';
res.status(status).json({status, message});
return
}
fs.readFile("./users.json", (err, data) => {
if (err) {
const status = 401
const message = err
res.status(status).json({status, message})
return
};
// Get current users data
var data = JSON.parse(data.toString());
// Get the id of last user
var last_item_id = data.users[data.users.length-1].id;
//Add new user
data.users.push({id: last_item_id + 1, email: email, password: password}); //add some data
var writeData = fs.writeFile("./users.json", JSON.stringify(data), (err, result) => { // WRITE
if (err) {
const status = 401
const message = err
res.status(status).json({status, message})
return
}
});
});
// Create token for new user
const access_token = createToken({email, password})
console.log("Access Token:" + access_token);
res.status(200).json({access_token})
})
// Login to one of the users from ./users.json
server.post('/auth/login', (req, res) => {
console.log("login endpoint called; request body:");
console.log(req.body);
const {email, password} = req.body;
if (isAuthenticated({email, password}) === false) {
const status = 401
const message = 'Incorrect email or password'
res.status(status).json({status, message})
return
}
const access_token = createToken({email, password})
console.log("Access Token:" + access_token);
res.status(200).json({access_token})
})
server.use(/^(?!\/auth).*$/, (req, res, next) => {
if (req.headers.authorization === undefined || req.headers.authorization.split(' ')[0] !== 'Bearer') {
const status = 401
const message = 'Error in authorization format'
res.status(status).json({status, message})
return
}
try {
let verifyTokenResult;
verifyTokenResult = verifyToken(req.headers.authorization.split(' ')[1]);
if (verifyTokenResult instanceof Error) {
const status = 401
const message = 'Access token not provided'
res.status(status).json({status, message})
return
}
next()
} catch (err) {
const status = 401
const message = 'Error access_token is revoked'
res.status(status).json({status, message})
}
})
server.use(router)
server.listen(8000, () => {
console.log('Run Auth API Server')
})

Impossible to read Kafka message value

I'm using a simple producer in order to produce a message in kafka.
This message is a json object.
This is the code of my producer :
const kafka = require('kafka-node');
const Producer = kafka.Producer;
const KeyedMessage = kafka.KeyedMessage;
const Offset = kafka.Offset;
const Client = kafka.Client;
const p = 0;
const a = 0;
const topic = 'claims';
const client = new Client('192.168.99.100:2181');
const producer = new Producer(client, { requireAcks: 1 });
producer.on('error', function (err) {
done.fail(err);
});
producer.on('ready', function () {
const message = {status: 'Created', id_claims: '12345'};
producer.send([
{ topic: topic, partition: p, messages: message, attributes: a }
], function (err, result) {
expect(result).not.toBeNull();
expect(err).toBeNull();
done();
});
});
But then when i try to read the content of this message I always get
[Object object]
This is the code of my consumer :
'use strict';
const util = require('util')
const kafka = require('kafka-node');
const Consumer = kafka.Consumer;
const Offset = kafka.Offset;
const Client = kafka.Client;
const topic = 'claims';
const client = new Client('192.168.99.100:2181');
const topics = [
{topic: topic, partition: 0}
];
const options = { autoCommit: false, fetchMaxWaitMs: 1000, fetchMaxBytes: 1024 * 1024 };
const consumer = new Consumer(client, topics, options);
const offset = new Offset(client);
consumer.on('message', function (message) {
console.log(JSON.stringify(message.value));
console.log("Value: %j", message.value);
console.log("Value: %o", message.value);
console.log('Value: ' + util.inspect(message.value, 10));
console.log(util.inspect(message.value, {showHidden: false, depth: null}));
});
consumer.on('error', function (err) {
console.log(err);
});
Every method I try in the handler return the same result : [Object object]
Problem lies in below shown code:
producer.send([
{ topic: topic, partition: p, messages: message, attributes: a }
], function (err, result) {
expect(result).not.toBeNull();
expect(err).toBeNull();
done();
});
From the documentation as shown below, you cannot supply object to messages. Supplying JSON.stringfy(message) to messages will work.
{
topic: 'topicName',
messages: ['message body'], // multi messages should be a array, single message can be just a string or a KeyedMessage instance
key: 'theKey', // string or buffer, only needed when using keyed partitioner
partition: 0, // default 0
attributes: 2, // default: 0
timestamp: Date.now() // <-- defaults to Date.now() (only available with kafka v0.10 and KafkaClient only)
}
Your producer message likely needs to have a value of:
JSON.stringify(message)