-
Notifications
You must be signed in to change notification settings - Fork 6
/
rnode-sign.ts
162 lines (144 loc) · 5.62 KB
/
rnode-sign.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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)