@tgrospic/rnode-grpc-js
Version:
RNode gRPC helpers
163 lines (144 loc) • 5.76 kB
text/typescript
import { blake2bHex } from 'blakejs'
import { ec } from 'elliptic'
import jspb from 'google-protobuf'
/**
* These deploy types are based on protobuf specification which must be
* used to create the hash and signature of deploy data.
*/
/**
* Deploy data (required for signing)
*/
export interface UnsignedDeployData {
readonly term: string
readonly timestamp: number
readonly phlolimit: number
readonly phloprice: number
readonly validafterblocknumber: number
readonly shardid: string
}
/**
* Signed DeployData object (protobuf specification)
* NOTE: Represents the same type as generated DeployData.
*/
export interface DeploySignedProto {
readonly term: string
readonly timestamp: number
readonly phlolimit: number
readonly phloprice: number
readonly validafterblocknumber: number
readonly shardid: string
readonly sigalgorithm: string
readonly deployer: Uint8Array
readonly sig: Uint8Array
}
/**
* Signs deploy data.
*
* The private key for signing can be in different formats supported by
* [elliptic](https://github.com/indutny/elliptic#ecdsa) library.
*
* **NOTE: Signing function can be used independently without this library and JS generated code (see _rnode-sign.ts_ source).**
*
* ```typescript
* // Generate new key pair
* const { ec } = require('elliptic')
* const secp256k1 = new ec('secp256k1')
* const key = secp256k1.genKeyPair()
*
* // Or use existing private key as hex string, Uint8Array, Buffer or ec.KeyPair
* const key = '1bf36a3d89c27ddef7955684b97667c75454317d8964528e57b2308947b250b0'
*
* const deployData = {
* term: 'new out(`rho:io:stdout`) in { out!("Browser deploy test") }',
* timestamp: Date.now(), // nonce
* phloprice: 1, // price of tinny REV for phlogiston (gas)
* phlolimit: 10e3, // max phlogiston (gas) for deploy execution
* validafterblocknumber: 123, // latest block number
* shardid: 'testnet', // shard name
* }
*
* // Signed deploy with deployer, sig and sigalgorithm fields populated
* const signed = signDeploy(key, deployData)
* ```
*/
export const signDeploy = function (privateKey: ec.KeyPair | string, deployObj: UnsignedDeployData): DeploySignedProto {
const {
term, timestamp, phlolimit, phloprice, validafterblocknumber, shardid,
} = deployObj
// Currently supported algorithm
const sigalgorithm = 'secp256k1'
// Serialize deploy data for signing
const deploySerialized = deployDataProtobufSerialize({
term, timestamp, phlolimit, phloprice, validafterblocknumber, shardid,
})
// Signing key
const crypt = new ec(sigalgorithm)
const key = getSignKey(crypt, privateKey)
const deployer = Uint8Array.from(key.getPublic('array'))
// Hash and sign serialized deploy
const hashed = blake2bHex(deploySerialized, void 666, 32)
const sigArray = key.sign(hashed, {canonical: true}).toDER()
const sig = Uint8Array.from(sigArray)
// Return deploy object / ready for sending to RNode
return {
term, timestamp, phlolimit, phloprice, validafterblocknumber, shardid,
sigalgorithm, deployer, sig,
}
}
/**
* Verifies deploy for a valid signature.
*/
export const verifyDeploy = (deployObj: DeploySignedProto) => {
const {
term, timestamp, phlolimit, phloprice, validafterblocknumber, shardid,
sigalgorithm, deployer, sig,
} = deployObj
// Serialize deploy data for signing
const deploySerialized = deployDataProtobufSerialize({
term, timestamp, phlolimit, phloprice, validafterblocknumber, shardid,
})
// Signing public key to verify
const crypt = new ec(sigalgorithm)
const key = crypt.keyFromPublic(deployer)
// Hash and verify signature
const hashed = blake2bHex(deploySerialized, void 666, 32)
const isValid = key.verify(hashed, sig)
return isValid
}
/**
* Serialization of DeployDataProto object without generated JS code.
*/
export const deployDataProtobufSerialize = (deployData: UnsignedDeployData) => {
const {term, timestamp, phlolimit, phloprice, validafterblocknumber, shardid} = deployData
// Create binary stream writer
const writer = new jspb.BinaryWriter()
// Write fields (protobuf doesn't serialize default values)
const writeString = (order: number, val: string) => val != "" && writer.writeString(order, val)
const writeInt64 = (order: number, val: number) => val != 0 && writer.writeInt64(order, val)
// https://github.com/rchain/rchain/blob/ebe4d476371/models/src/main/protobuf/CasperMessage.proto#L134-L149
// message DeployDataProto {
// bytes deployer = 1; //public key
// string term = 2; //rholang source code to deploy (will be parsed into `Par`)
// int64 timestamp = 3; //millisecond timestamp
// bytes sig = 4; //signature of (hash(term) + timestamp) using private key
// string sigAlgorithm = 5; //name of the algorithm used to sign
// int64 phloPrice = 7; //phlo price
// int64 phloLimit = 8; //phlo limit for the deployment
// int64 validAfterBlockNumber = 10;
// string shardId = 11;//shard ID to prevent replay of deploys between shards
// }
// Serialize fields
writeString(2, term)
writeInt64(3, timestamp)
writeInt64(7, phloprice)
writeInt64(8, phlolimit)
writeInt64(10, validafterblocknumber)
writeString(11, shardid)
return writer.getResultBuffer()
}
/**
* Fix for ec.keyFromPrivate not accepting KeyPair.
* - detect KeyPair if it have `sign` function
*/
const getSignKey = (crypt: ec, pk: ec.KeyPair | string) =>
pk && typeof pk != 'string' && pk.sign && pk.sign.constructor == Function ? pk : crypt.keyFromPrivate(pk)