Smart Contract
cBridge
A.08dd120226ec2213.cBridge
1import Pb from 0x08dd120226ec2213
2
3// only need to support signer verify for now
4access(all) contract cBridge {
5
6 access(all) event SignersUpdated(
7 signers: {String: UInt256}
8 )
9
10 // path for admin resource
11 access(all) let AdminPath: StoragePath
12 // unique chainid required by cbridge system
13 access(all) let chainID: UInt64
14 access(contract) let domainPrefix: [UInt8]
15 // save map from pubkey to power, only visible in this contract to extra safety
16 access(contract) var signers: {String: UInt256}
17 access(all) var totalPower: UInt256
18 // block height when signers are last updated by sigs
19 // flow block.timestamp is UFix64 so hard to use
20 access(all) var signerUpdateBlock: UInt64
21
22 // at first we require pubkey to be also present in SignerSig, so verify can just do map lookup in signers
23 // but this make other components in the system to be aware of pubkeys which is hard to do
24 // so without pubkey, we just loop over all signers and call verify, it's less efficient but won't be an issue
25 // as there won't be many signers. we keep struct for future flexibility but pubkey field is NOT needed.
26 access(all) struct SignerSig {
27 access(all) let sig: [UInt8]
28 init(sig: [UInt8]) {
29 self.sig = sig
30 }
31 }
32
33 // Admin is a special authorization resource that
34 // allows the owner to perform important functions to modify the contract state
35 access(all) resource BridgeAdmin {
36 access(all) fun resetSigners(signers: {String: UInt256}) {
37 cBridge.signers = signers
38 cBridge.totalPower = 0
39 for power in signers.values {
40 cBridge.totalPower = cBridge.totalPower + power
41 }
42 emit SignersUpdated(
43 signers: signers
44 )
45 }
46 access(all) fun createBridgeAdmin(): @BridgeAdmin {
47 return <-create BridgeAdmin()
48 }
49 }
50
51 // getter for signers/power as self.signers is not accessible outside contract
52 access(all) view fun getSigners(): {String: UInt256} {
53 return self.signers
54 }
55
56 // return true if sigs have more than 2/3 total power
57 access(all) fun verify(data:[UInt8], sigs: [SignerSig]): Bool {
58 var signedPower: UInt256 = 0
59 var quorum: UInt256 = (self.totalPower*2)/3 + 1
60 // go over all known signers, for each signer, try to find which sig matches
61 for pubkey in self.signers.keys {
62 let pk = PublicKey(
63 publicKey: pubkey.decodeHex(),
64 signatureAlgorithm: SignatureAlgorithm.ECDSA_secp256k1
65 )
66 // for this pk, try to find the matched sig
67 // flow doc says `for index, element in array` but it's only in master https://github.com/onflow/cadence/issues/1230
68 var idx = -1
69 for ssIdx, ss in sigs {
70 if pk.verify(
71 signature: ss.sig,
72 signedData: data,
73 domainSeparationTag: "FLOW-V0.0-user",
74 hashAlgorithm: HashAlgorithm.SHA3_256
75 ) {
76 signedPower = signedPower + self.signers[pubkey]!
77 if signedPower >= quorum {
78 return true
79 }
80 // delete ss from sigs
81 idx = ssIdx
82 // now we break the sig loop to check next signer as we only allow one sig per signer
83 break
84 }
85 }
86 if idx >=0 {
87 sigs.remove(at: idx)
88 }
89 }
90 return false
91 }
92
93 init(chID:UInt64) {
94 self.chainID = chID
95 // domainPrefix is chainID big endianbytes followed by "A.xxxxxx.cBridge".utf8, xxxx is this contract account
96 self.domainPrefix = chID.toBigEndianBytes().concat(self.getType().identifier.utf8)
97
98 self.signers = {}
99 self.totalPower = 0
100
101 self.signerUpdateBlock = 0
102
103 self.AdminPath = /storage/cBridgeAdmin
104 self.account.storage.save<@BridgeAdmin>(<- create BridgeAdmin(), to: self.AdminPath)
105 }
106
107 // below are struct and func needed to update cBridge.signers by cosigned msg
108 // blockheight is like nonce to avoid replay attack
109 /* proto definition
110 message PbSignerPowerMap {
111 uint64 blockheight = 1; // block when this pb was proposed by sgn, must < currentblock
112 repeated SignerPower = 2;
113 }
114 message SignerPower {
115 bytes pubkey = 1; // raw bytes of pubkey, 64 bytes
116 bytes power = 2; // big.Int.Bytes
117 }
118 */
119 access(all) struct PbSignerPowerMap {
120 access(all) var blockheight: UInt64
121 access(all) let signerMap: {String: UInt256}
122 access(all) var totalPower: UInt256
123 // raw is serialized PbSignerPowerMap
124 init(_ raw: [UInt8]) {
125 self.blockheight = 0
126 self.signerMap = {}
127 self.totalPower = 0
128 // now decode pbraw and assign
129 let buf = Pb.Buffer(raw: raw)
130 while buf.hasMore() {
131 let tagType = buf.decKey()
132 switch Int(tagType.tag) {
133 case 1:
134 assert(tagType.wt==Pb.WireType.Varint, message: "wrong wire type")
135 self.blockheight = buf.decVarint()
136 case 2:
137 assert(tagType.wt==Pb.WireType.LengthDelim, message: "wrong wire type")
138 // now decode SignerPower msg
139 // pegbridge.pb.go, as the proto code show, repeated field has a key outside
140 let tag0 = buf.decKey()
141 let tag1 = buf.decKey()
142 assert(tag1.tag==1, message: "tag not 1: ".concat(tag1.tag.toString()))
143 assert(tag1.wt==Pb.WireType.LengthDelim, message: "wrong wire type")
144 let pbhex = Pb.toString(buf.decBytes())
145 let tag2 = buf.decKey()
146 assert(tag2.tag==2, message: "tag not 2: ".concat(tag2.tag.toString()))
147 assert(tag2.wt==Pb.WireType.LengthDelim, message: "wrong wire type")
148 let power = Pb.toUInt256(buf.decBytes())
149 // add to map
150 self.signerMap[pbhex] = power
151 self.totalPower = self.totalPower + power
152 default:
153 assert(false, message: "unsupported tag")
154 }
155 }
156 }
157 }
158 // new signers can be updated if cosigned by existing signers w/ 2/3 power
159 // pbmsg is serialized PbSignerPowerMap
160 access(all) fun updateSigners(pbmsg:[UInt8], sigs: [SignerSig]) {
161 let domain = self.domainPrefix.concat(".UpdateSigners".utf8)
162 assert(self.verify(data: domain.concat(pbmsg), sigs: sigs), message: "verify sigs failed")
163 let newSigners = PbSignerPowerMap(pbmsg)
164 assert(newSigners.totalPower > 0, message: "total power must > 0")
165 assert(newSigners.blockheight > self.signerUpdateBlock, message: "new signer timestamp must be larger")
166 assert(newSigners.blockheight <= getCurrentBlock().height, message: "new signer timestamp must <= current")
167 // update signerUpdateBlock and signers
168 self.signerUpdateBlock = getCurrentBlock().height
169 self.totalPower = newSigners.totalPower
170 self.signers = newSigners.signerMap
171
172 emit SignersUpdated(
173 signers: newSigners.signerMap
174 )
175 }
176}
177