mirror of
https://github.com/danog/toncontest.git
synced 2024-11-29 20:19:11 +01:00
add
This commit is contained in:
parent
2bc55abb62
commit
84edf128a3
@ -1,182 +0,0 @@
|
||||
# Multisignature wallet
|
||||
|
||||
Daniil Gentili's submission (@danogentili, <daniil@daniil.it>).
|
||||
|
||||
Upgradable multisignature wallet, with custom scripts to deserialize and inspect the contents of BOC files containing TL-B Message constructors, create, sign and verify wallet requests and wallet code upgrades.
|
||||
All custom data structures used in the wallet can be viewed as a custom TL-B scheme in proto/scheme.tlb (some basic TON constructors are also included for reference).
|
||||
Most smart contact get-methods (except for the basic seqno and getPartials methods) return an integer, indicating whether the operation was successful and the requested data was found, followed by a cell/integer with the found data (or an empty cell/0 in case of failure).
|
||||
|
||||
## Project structure
|
||||
|
||||
Fift scripts:
|
||||
* `lib.fif` - Library with functions to deserialize and inspect contents of TON messages
|
||||
The external TL-B Message X object is deserialized, with info printed to stdout.
|
||||
Then, the custom `multiSigWrapper`, `wrappedMessage`, `modeMessage` and `codeMessage` TL-B objects are deserialized, with info printed to stdout.
|
||||
In the case of `modeMessage` (simple message to be sent by wallet once enough signatures are gathered), a the internal Message X object is also unpacked, with info printed to stdout.
|
||||
In the case of `codeMessage`, the new public key dictionary is unpacked an printed to stdout, along with a csr dump of the code slice and other info.
|
||||
* `test.fif` - TVM testing platform
|
||||
```
|
||||
usage: test.fif <init-message> <message> <func> [ <message2> <func2> ... ]
|
||||
```
|
||||
|
||||
Runs the function with ID <func> in the TON VM, using initial code and storage data from <init-message>.boc and <message>.boc as message (OR as the only integer parameter of get-method, if <message>.boc does not exist).
|
||||
The TVM persistent storage is dumped to console and reused after each method call, allowing detailed debugging of smart contracts, with multiple method calls altering the contract's persistent storage.
|
||||
|
||||
* `gen-pub.fif` - Generates public/private keypair
|
||||
```
|
||||
usage: gen-pub.fif <privkey>
|
||||
```
|
||||
|
||||
Create public key files from private keys; if `<privkey>` doesn't exist, it will be created.
|
||||
Will also print the hex public key ID.
|
||||
|
||||
* `wallet-create.fif` - Creates shared wallet
|
||||
```
|
||||
usage: wallet-create.fif <workchain-id> <wallet-name> <n> <k> <privkey1> [<pubkey2> ...]
|
||||
```
|
||||
|
||||
Creates a new multisignature wallet in specified workchain composed of `<n>` (1-10) keys.
|
||||
The first of the keys must be a private key (pre-existing or not), used to generate the wallet; the rest MUST be public keys.
|
||||
|
||||
Min `<k>` (1-10) signatures required to send an order; load `<n>` pre-existing public keys from files `<key1...n>`.
|
||||
|
||||
* `wallet-update.fif` - Creates wallet code update request
|
||||
```
|
||||
usage: wallet-update.fif <wallet-name> <wallet-update> <seqno> <n> <k> <key-id> <privkey1> [<pubkey2> ...]
|
||||
```
|
||||
|
||||
Updates an existing multisignature wallet.
|
||||
The first of the keys must be a private key (with ID `<key-id>`), used to sign the wallet update request saved to `<wallet-update>.boc`; the rest MUST be public keys.
|
||||
Create or generate public key files from private keys using gen-pub.fif privkey
|
||||
|
||||
Min `<k>` (1-10) signatures required to send an order; load `<n>` pre-existing public keys from files `<key1...n>`.
|
||||
|
||||
* `create.fif` - Creates simple message to be sent to wallet
|
||||
```
|
||||
usage: create.fif <filename-base> <key> <key-id> <dest-addr> <seqno> <amount> [-B <body-boc>] [<savefile>]
|
||||
```
|
||||
|
||||
Creates a request to shared wallet created by wallet-create.fif, with private key `<key-id>` loaded from file `<key>.pk` and address from `<filename-base>.addr`, and saves it into `<savefile>.boc` ('wallet-query.boc' by default)
|
||||
|
||||
* `sign.fif` - Adds signature to wallet message
|
||||
```
|
||||
usage: sign.fif <input> <output> <key> <key-id>
|
||||
```
|
||||
|
||||
Signs multisig `<input>.boc` file with private key `<key-id>` loaded from file `<key>.pk` and writes result to `<output>.boc`
|
||||
|
||||
* `verify.fif` - Verifies signature of message against known public key(s)
|
||||
```
|
||||
usage: verify.fif <input> <key1> <key1-id> [<key2> <key2-id> ...]
|
||||
```
|
||||
|
||||
Verify multisig `<input>.boc` file with public key `<keyN-id>` loaded from file `<keyN>.pubkey`
|
||||
|
||||
* `merge.fif` - Merges multiple messages with same content and different signatures
|
||||
```
|
||||
usage: merge.fif <input1> <input2> [<input3> ...] <output>
|
||||
```
|
||||
|
||||
Merges multisig `<inputx>.boc` files and writes result to `<output>.boc`
|
||||
|
||||
* `inspect.fif` - Inspects the contents of request
|
||||
```
|
||||
usage: inspect.fif <input>
|
||||
```
|
||||
|
||||
Inspects contents of multisig `<input>.boc` file.
|
||||
|
||||
|
||||
FunC code:
|
||||
* `wallet-code.fc` - Wallet code
|
||||
* `wallet-code-update.fc` - Wallet code with minor change to test wallet code upgrade
|
||||
|
||||
Scripts:
|
||||
* `test.sh` - Creates wallet, set of signed requests and tests wallet in TON VM
|
||||
* `test-update.sh` - Tests wallet code upgrade functionality (after `test.sh`) in TON VM
|
||||
|
||||
## Testing
|
||||
|
||||
The `test.sh` script contains a full set of commands to test every fift script.
|
||||
You might want to run each command inside `test.sh` separately (there are also multiple comments explaining what each line does in detail), or run `./test.sh | less` to be able to better review the output of each command.
|
||||
|
||||
The script starts by creating ten public/private keypairs using `gen-pub.fif`:
|
||||
```
|
||||
for f in {a..j}; do fift -s ../gen-pub.fif $f;done
|
||||
```
|
||||
|
||||
Next, the script creates a constructor message for the shared wallet smart contract using `wallet-create.fif`, creating a `pony-create.boc` file:
|
||||
```
|
||||
fift -s ../wallet-create.fif 0 pony 10 10 {a..j} | tee log
|
||||
```
|
||||
|
||||
After extracting the computed wallet address from `log`, the scripts generates an initial wallet request, requesting to send 10 grams to itself (just as a test, saving to `a.boc`).
|
||||
```
|
||||
fift -s ../create.fif pony a 0 $address 0 10 a
|
||||
```
|
||||
|
||||
Here, note the `a 0`: to save space, I have chosen to not use the entire ecdh key as key in the signature dictionary.
|
||||
Instead, a **key ID** is used to distinguish signatures made by certain keys: this is a simple 4-bit value (instead of 256 bits!), equal to the position of the key in the `wallet-create` argument list.
|
||||
In this case, the `a` key was the first key (`{a..j}` in Bash is shorthand for `a b c .. j`), so the ID is `0`.
|
||||
What follows is the address, the seqno, the amount of grams and the savefile (`a`) for the query.
|
||||
|
||||
|
||||
Some helper Bash functions are defined:
|
||||
```
|
||||
chr() { [ "$1" -lt 256 ] || return 1; printf "\\$(printf '%03o' "$1")"; }
|
||||
ord() { LC_CTYPE=C printf '%d' "'$1"; }
|
||||
```
|
||||
|
||||
Sign the query using all keys separately, creating eight more boc files, each signed by TWO keys only (key `a` and key `{b..j}`):
|
||||
```
|
||||
for f in {1..9}; do fift -s ../sign.fif a $(chr $((97+f))) $(chr $((97+f))) $f;done
|
||||
```
|
||||
|
||||
Merge all queries:
|
||||
```
|
||||
fift -s ../merge.fif {a..j} merge
|
||||
```
|
||||
|
||||
Inspect the merged query:
|
||||
```
|
||||
fift -s ../inspect.fif merge
|
||||
```
|
||||
|
||||
|
||||
Finally run the generated files in the VM in the following order
|
||||
|
||||
* First init VM with constructor message
|
||||
* Then load the first file with only one signature by key a (ID 0, method -1)
|
||||
* Run seqno get-method (method 85143)
|
||||
* Run getPartialsByKeyId get-method (method 113609)
|
||||
* Load file with all signatures (and send message, method -1)
|
||||
* Run getPartialsByKeyId get-method (method 113609)
|
||||
|
||||
```
|
||||
fift -s ../test.fif \
|
||||
pony-create \
|
||||
a -1 \
|
||||
0 85143 \
|
||||
0 113609 \
|
||||
merge -1 \
|
||||
0 113609
|
||||
```
|
||||
|
||||
## Upgrading code
|
||||
|
||||
`test-update.sh` should be run after `test.sh`, it executes a similar set of operations:
|
||||
|
||||
* The wallet is initialized as before (10 signatures required to send message)
|
||||
* A simple message is created with only 1 signature (`a`), sending 10 grams to the wallet itself
|
||||
* This message is sent to the wallet and stored, waiting for further (9 more) signatures
|
||||
* A **code upgrade** message is created with only 1 signature (`a`): the upgrade sets to 3 the minimum number of required signatures
|
||||
* This message is sent to the wallet and stored, waiting for further (9 more) signatures
|
||||
* 9 more signatures (`b-j`) are appended to the **code upgrade** message
|
||||
* The full **code upgrade** message is sent to the wallet, setting to **3** the minimum number of signatures required to send the message, also modifying the code by adding a new method.
|
||||
|
||||
This code upgrade message, however, was signed by all **10** users, so it's really just a simpler way to upgrade a wallet: instead of creating new wallet => moving all funds to new upgraded wallet, a simple code upgrade message is signed.
|
||||
* 2 more signatures (`b`, `c`) are appended to the **simple** message
|
||||
* The simple message with additional signatures (`b`, `c`) is sent to the wallet, sending out wallet funds since all **3 required signatures** are gathered.
|
||||
|
||||
If we were running on the blockchain, at this point the smart contract code root would be updated to point to the new code, allowing us to use the new `getMagic` (77784) method that returns (420, 69) when called.
|
||||
Since we're running in a local TVM instance, and fift's `runvm` primitives have no way of returning the output action list (including changes to the code), the code isn't actually updated, but the modified signature list and minSig data structures in the persistent contract storage are indeed returned to the fift stack and re-used for the next TVM method call, allowing us to test the feature.
|
@ -1,92 +0,0 @@
|
||||
#!/usr/bin/env -S fift -s
|
||||
"TonUtil.fif" include
|
||||
|
||||
{
|
||||
."usage: " @' $0 type ." <filename-base> <key> <key-id> <dest-addr> <seqno> <amount> [-B <body-boc>] [<savefile>]" cr
|
||||
."Creates a request to shared wallet created by wallet-create.fif, with private key <key-id> loaded from file <key>.pk "
|
||||
."and address from <filename-base>.addr, and saves it into <savefile>.boc ('wallet-query.boc' by default)" cr 1 halt
|
||||
} : usage
|
||||
def? $8 { @' $7 "-B" $= { @' $8 =: body-boc-file [forget] $8 def? $9 { @' $9 =: $7 [forget] $9 } { [forget] $7 } cond
|
||||
@' $# 2- =: $# } if } if
|
||||
$# 6 < $# 9 > or ' usage if
|
||||
true constant bounce
|
||||
|
||||
$1 =: file-base
|
||||
$2 =: key
|
||||
$3 parse-int =: key-id
|
||||
$4 bounce parse-load-address =: bounce 2=: dest_addr
|
||||
$5 parse-int =: seqno
|
||||
$6 $>GR =: amount
|
||||
def? $7 { @' $7 } { "wallet-query" } cond constant savefile
|
||||
3 constant send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors
|
||||
3 86400 * constant timeout // external message expires in 3 days
|
||||
|
||||
file-base +".addr" load-address
|
||||
2dup 2constant wallet-addr
|
||||
."Source wallet address = " 2dup .addr cr 6 .Addr cr
|
||||
key +".pk" load-keypair nip constant wallet_pk
|
||||
|
||||
def? body-boc-file { @' body-boc-file file>B B>boc } { <b 0 32 u, "TESTv2" $, b> } cond
|
||||
constant body-cell
|
||||
|
||||
."Transferring " amount .GR ."to account "
|
||||
dest_addr 2dup bounce 7 + .Addr ." = " .addr
|
||||
."seqno=0x" seqno x. ."bounce=" bounce . cr
|
||||
."Body of transfer message is " body-cell <s csr. cr
|
||||
|
||||
// create a message
|
||||
<b b{01} s, bounce 1 i, b{000100} s, dest_addr addr, amount Gram, 0 9 64 32 + + 1+ u,
|
||||
body-cell <s 2dup s-fits? not rot over 1 i, -rot { drop body-cell ref, } { s, } cond
|
||||
b>
|
||||
// create wrapper message
|
||||
// modeMessage$0 mode:uint8 body:^(Message X) = ModeMessage X;
|
||||
// wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X;
|
||||
<b now timeout + 32 u, seqno 32 u, 0 1 u, send-mode 8 u, swap ref, b>
|
||||
|
||||
dup ."signing message: " <s csr. cr
|
||||
dup hashu
|
||||
dup ."Hash: " x. cr cr
|
||||
wallet_pk ed25519_sign_uint
|
||||
|
||||
// signature$_ R:bits256 s:bits256 = Signature;
|
||||
256 B>u@+ swap 256 B>u@ swap
|
||||
<b swap 256 u, swap 256 u, b>
|
||||
|
||||
// key ID => signature
|
||||
// multiSigWrapper$0 signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapper X;
|
||||
<b
|
||||
0 1 u, // $0
|
||||
swap <s key-id dictnew 4 udict! // Create and populate dictionary
|
||||
not abort"Failure inserting signature!"
|
||||
|
||||
dict, // signatures
|
||||
swap <s s, // message
|
||||
b>
|
||||
|
||||
//
|
||||
// addr_none$00 = MsgAddressExt;
|
||||
// addr_extern$01 len:(## 9) external_address:(bits len)
|
||||
// = MsgAddressExt;
|
||||
// anycast_info$_ depth:(#<= 30) { depth >= 1 }
|
||||
// rewrite_pfx:(bits depth) = Anycast;
|
||||
// addr_std$10 anycast:(Maybe Anycast)
|
||||
// workchain_id:int8 address:bits256 = MsgAddressInt;
|
||||
// addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9)
|
||||
// workchain_id:int32 address:(bits addr_len) = MsgAddressInt;
|
||||
//
|
||||
// ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt
|
||||
// import_fee:Grams = CommonMsgInfo;
|
||||
//
|
||||
//
|
||||
// message$_ {X:Type} info:CommonMsgInfo
|
||||
// init:(Maybe (Either StateInit ^StateInit))
|
||||
// body:(Either X ^X) = Message X;
|
||||
//
|
||||
<b b{1000100} s, wallet-addr addr, 0 Gram, b{00} s,
|
||||
swap <s s, b>
|
||||
|
||||
dup ."resulting external message: " <s csr. cr
|
||||
2 boc+>B dup Bx. cr
|
||||
savefile +".boc" tuck B>file
|
||||
."Query expires in " timeout . ."seconds" cr
|
||||
."(Saved to file " type .")" cr
|
@ -1,15 +0,0 @@
|
||||
"TonUtil.fif" include
|
||||
|
||||
{ ."usage: " @' $0 type ." <privkey>" cr
|
||||
."Create public key files from private keys; if <privkey> doesn't exist, it will be created." cr cr
|
||||
1 halt
|
||||
} : usage
|
||||
$# 1 < ' usage if
|
||||
|
||||
$1 +".pk" load-generate-keypair drop
|
||||
dup $1 +".pubkey" B>file
|
||||
|
||||
|
||||
."Wrote private key to " $1 +".pk" type cr
|
||||
."Wrote public key to " $1 +".pubkey" type cr cr
|
||||
."Public key: " Bx. cr cr
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
."usage: " @' $0 type ." <input>" cr
|
||||
."Inspects contents of multisig <input>.boc file." cr 1 halt
|
||||
} : usage
|
||||
$# 1 < ' usage if
|
||||
|
||||
"lib.fif" include
|
||||
|
||||
$1 +".boc" load-boc inspect
|
@ -1,235 +0,0 @@
|
||||
"TonUtil.fif" include
|
||||
|
||||
// b body -- b'
|
||||
{ tuck <s 2dup s-fits? not rot over 1 i, -rot
|
||||
{ drop swap ref, } { s, nip } cond
|
||||
} : maybe-ref,
|
||||
|
||||
// filename -- c
|
||||
{ dup ."Loading order from file " type ."..." cr
|
||||
file>B B>boc
|
||||
} : load-boc
|
||||
|
||||
// filename -- uint256
|
||||
{ dup ."Loading public key from file " type ."..." cr
|
||||
file>B dup Blen 32 <> abort"Public key must be exactly 32 bytes long"
|
||||
256 B>u@
|
||||
} : load-pubkey
|
||||
|
||||
' constant : const
|
||||
' 2constant : 2const
|
||||
|
||||
// D n -- uint
|
||||
{
|
||||
0 -rot
|
||||
{ drop swap 1+ swap -1 } dictmap drop
|
||||
} : dictlen
|
||||
|
||||
// u D n -- s ? or ?
|
||||
{
|
||||
{ swap 2 pick = { nip 0 } { drop -1 } cond } dictforeach
|
||||
dup ' nip if
|
||||
not
|
||||
} : dict[]
|
||||
|
||||
{ hole dup 1 ' @ does create 1 ' ! does create } : variable-set
|
||||
{ hole hole 2dup 2 { @ swap @ swap } does create 2 { rot swap ! ! } does create } : 2variable-set
|
||||
2variable-set wallet-addr wallet-addr!
|
||||
variable-set message-hash message-hash!
|
||||
variable-set message-contents message-contents!
|
||||
variable-set sig-count sig-count!
|
||||
|
||||
// udata BSig ukey – ?
|
||||
{
|
||||
256 u>B rot 256 u>B -rot ed25519_chksign
|
||||
} : ed25519_chksignuu
|
||||
|
||||
|
||||
// Inspect multisigwrapper starting from signatures
|
||||
//
|
||||
// multiSigWrapper$_ signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X;
|
||||
//
|
||||
// s --
|
||||
{
|
||||
dict@+ swap
|
||||
dup null? abort"Empty signature list!"
|
||||
dup 4 dictlen sig-count!
|
||||
|
||||
."Signed by the following keys: "
|
||||
4 { drop . ."- " -1 } dictforeach cr drop
|
||||
|
||||
."Hash: " dup s>c hashu dup x. cr
|
||||
message-hash!
|
||||
|
||||
// modeMessage$0 mode:uint8 body:^(Message X) = ModeMessage X;
|
||||
// wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X;
|
||||
|
||||
32 u@+ swap
|
||||
dup ."Expires: " .
|
||||
dup now < { ."(already EXPIRED!)" drop } { ."(in " now - . ."seconds)" } cond cr
|
||||
|
||||
32 u@+ swap
|
||||
."Seqno: " . cr
|
||||
|
||||
1 u@+ swap
|
||||
{
|
||||
."Is code message!" cr
|
||||
// codeMessage$1 minSigs:(## 4) keys:(HashmapE 4 PubKey) code:^Cell = ModeMessage X;
|
||||
|
||||
4 u@+ swap
|
||||
."Minsigs: " . cr
|
||||
|
||||
dict@+ swap
|
||||
dup null? abort"Empty key list!"
|
||||
|
||||
."The following keys are present: " cr
|
||||
4 { swap ."* " . ."- " 32 B@ Bx. cr -1 } dictforeach cr drop
|
||||
|
||||
ref@
|
||||
."Code: " <s csr.
|
||||
}
|
||||
{
|
||||
8 u@+ swap
|
||||
."Mode: " . cr
|
||||
|
||||
// Now on to the actual message we're agreeing to sign
|
||||
//
|
||||
// int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
|
||||
// src:MsgAddressInt dest:MsgAddressInt
|
||||
// value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
|
||||
// created_lt:uint64 created_at:uint32 = CommonMsgInfo;
|
||||
// ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt
|
||||
// import_fee:Grams = CommonMsgInfo;
|
||||
// ext_out_msg_info$11 src:MsgAddressInt dest:MsgAddressExt
|
||||
// created_lt:uint64 created_at:uint32 = CommonMsgInfo;
|
||||
."=>" cr ref@ <s
|
||||
|
||||
1 u@+ swap
|
||||
{ // External message *$1*
|
||||
."Inside: external message" cr
|
||||
}
|
||||
{ // Internal message int_msg_info$0
|
||||
."Inside: internal message" cr
|
||||
1 u@+ swap
|
||||
."Instant hypercube routing disabled? " . cr
|
||||
|
||||
1 u@+ swap
|
||||
."Bounce flag set? " . cr
|
||||
|
||||
1 u@+ swap
|
||||
// ."Bounced flag set? " . cr
|
||||
drop
|
||||
|
||||
2 u@+ nip // Drop src address constructor + flags
|
||||
|
||||
3 u@+ swap // Read dst address constructor + flags
|
||||
// addr_std$10 anycast 0 => 100 => 4
|
||||
4 <> abort"Unsupported address!" // Make things simple for now
|
||||
|
||||
8 i@+
|
||||
256 u@+ -rot
|
||||
."Destination address: " .addr cr
|
||||
|
||||
Gram@+ swap
|
||||
."Grams: " .GR cr
|
||||
} cond
|
||||
drop
|
||||
} cond
|
||||
} : inspect-multisig
|
||||
|
||||
// Inspects contents of cell provided on top of the stack
|
||||
// c --
|
||||
//
|
||||
// defines/updates the following vars
|
||||
//
|
||||
// wallet-addr = u u
|
||||
// message-hash = B
|
||||
// message-contents = s
|
||||
{
|
||||
// Assuming the same external structure created by create.fif (simple external message)
|
||||
//
|
||||
// addr_none$00 = MsgAddressExt;
|
||||
// addr_extern$01 len:(## 9) external_address:(bits len)
|
||||
// = MsgAddressExt;
|
||||
// anycast_info$_ depth:(#<= 30) { depth >= 1 }
|
||||
// rewrite_pfx:(bits depth) = Anycast;
|
||||
// addr_std$10 anycast:(Maybe Anycast)
|
||||
// workchain_id:int8 address:bits256 = MsgAddressInt;
|
||||
// addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9)
|
||||
// workchain_id:int32 address:(bits addr_len) = MsgAddressInt;
|
||||
//
|
||||
// ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt
|
||||
// import_fee:Grams = CommonMsgInfo;
|
||||
//
|
||||
//
|
||||
// message$_ {X:Type} info:CommonMsgInfo
|
||||
// init:(Maybe (Either StateInit ^StateInit))
|
||||
// body:(Either X ^X) = Message X;
|
||||
//
|
||||
// <b b{1000100} s, wallet-addr addr, 0 Gram, b{00} s,
|
||||
// swap <s s, b>
|
||||
// (external message)
|
||||
//
|
||||
|
||||
|
||||
// message$_ {X:Type} info:CommonMsgInfo
|
||||
// init:(Maybe (Either StateInit ^StateInit))
|
||||
// body:(Either X ^X) = Message X;
|
||||
<s dup csr. cr
|
||||
// External message
|
||||
// ext_in_msg_info$10 src:MsgAddressExt (addr_none$00) dest:MsgAddressInt (addr_std$10 anycast 0) ... import_fee:Grams = CommonMsgInfo;
|
||||
// 10 00 10 0
|
||||
// 1000100 => 68
|
||||
7 u@+ swap 68 <> { ."There seems to be an invalid header" cr } if
|
||||
|
||||
8 i@+
|
||||
256 u@+ -rot
|
||||
2dup wallet-addr!
|
||||
."Wallet address: " .addr cr
|
||||
|
||||
Gram@+ nip // Ignore grams
|
||||
|
||||
1 u@+ swap // init:(Maybe
|
||||
abort"This seems to be an init message"
|
||||
|
||||
// body:(Either X ^X)
|
||||
1 u@+ swap
|
||||
{ ref@ <s } if // Load ref
|
||||
dup message-contents!
|
||||
|
||||
// multiSigWrapper$0 signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapper X;
|
||||
1 u@+ swap
|
||||
dup ."Message version: " . cr
|
||||
abort"Unsupported message version!"
|
||||
|
||||
inspect-multisig
|
||||
} : inspect
|
||||
|
||||
// storage$_ seqno:uint32 minSigs:(## 4) keys:(HashmapE 4 PubKey) messages:(HashmapE 256 (MultiSigWrapperStorage X)) = Storage X;
|
||||
// c --
|
||||
{
|
||||
<s
|
||||
32 u@+ swap
|
||||
."Seqno: " . cr
|
||||
|
||||
4 u@+ swap
|
||||
."Minsigs: " . cr
|
||||
|
||||
dict@+ swap
|
||||
."Keys: " 4 dictlen . cr
|
||||
|
||||
dict@
|
||||
."Messages: " dup 256 dictlen . cr
|
||||
|
||||
256
|
||||
{
|
||||
cr ."Inspecting message " swap x. cr
|
||||
|
||||
4 u@+ swap
|
||||
."Signature count: " dup . cr
|
||||
sig-count!
|
||||
|
||||
inspect-multisig
|
||||
-1
|
||||
} dictforeach drop
|
||||
} : inspect-storage
|
@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env -S fift -s
|
||||
"TonUtil.fif" include
|
||||
"lib.fif" include
|
||||
|
||||
{
|
||||
."usage: " @' $0 type ." <input1> <input2> [<input3> ...] <output>" cr
|
||||
."Merges multisig <inputx>.boc files and writes result to <output>.boc" cr 1 halt
|
||||
} : usage
|
||||
$# 3 < ' usage if
|
||||
|
||||
$# 1 - const input-len
|
||||
1 { dup $() +".boc" swap 1+ } input-len times drop
|
||||
input-len tuple const input-files
|
||||
|
||||
$# $() +".boc" const output-file
|
||||
|
||||
."Inspecting and merging " input-len . ."files..." cr
|
||||
input-files explode 1- swap
|
||||
load-boc inspect cr
|
||||
message-hash const previous-hash
|
||||
wallet-addr 2const previous-address
|
||||
|
||||
// multiSigWrapper$0 signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapper X;
|
||||
message-contents
|
||||
1 u@+ nip // $0 was already verified
|
||||
dict@+ // Read signatures
|
||||
s>c const wrapped-message // Store rest of message in const
|
||||
swap // put length on top, signatures right underneath
|
||||
|
||||
{ swap // Get filename, signatures right underneath
|
||||
load-boc inspect cr
|
||||
message-hash previous-hash <> abort"Hash mismatch!"
|
||||
// I suppose it's ok if there are multiple wallet instances with the same messages on different workchains/at different addresses, we're all agreeing to sign the same message anyway
|
||||
// wallet-address previous-address rot <> abort"Wallet address mismatch!" <> abort"Wallet address mismatch (different workchain)!"
|
||||
message-contents
|
||||
1 u@+ nip // $0 was already verified
|
||||
dict@ // Read signatures
|
||||
4 { drop s, -1 } dictmerge
|
||||
} swap times
|
||||
|
||||
// Signatures are the only element on the stack
|
||||
."Final message signed by the following keys: "
|
||||
dup 4 { drop . ."- " -1 } dictforeach cr drop
|
||||
|
||||
// multiSigWrapper$0 signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapper X;
|
||||
<b 0 1 u, swap dict, wrapped-message <s s, b>
|
||||
|
||||
// Renerate external message
|
||||
<b 68 7 u, wallet-addr addr, 0 Gram, 0 1 u, swap maybe-ref, b>
|
||||
|
||||
2 boc+>B dup Bx. cr
|
||||
output-file tuck B>file
|
||||
."Saved new multisigned message to file " type cr
|
@ -1,50 +0,0 @@
|
||||
#!/usr/bin/env -S fift -s
|
||||
"TonUtil.fif" include
|
||||
"lib.fif" include
|
||||
|
||||
{
|
||||
."usage: " @' $0 type ." <input> <output> <key> <key-id>" cr
|
||||
."Signs multisig <input>.boc file with private key <key-id> loaded from file <key>.pk and writes result to <output>.boc" cr 1 halt
|
||||
} : usage
|
||||
$# 4 < ' usage if
|
||||
|
||||
$1 =: input-file
|
||||
$2 =: output-file
|
||||
$3 =: key
|
||||
$4 parse-int =: key-id
|
||||
|
||||
input-file +".boc" load-boc constant order
|
||||
key +".pk" load-keypair nip constant wallet_pk
|
||||
|
||||
order inspect cr
|
||||
|
||||
// multiSigWrapper$0 signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapper X;
|
||||
message-contents
|
||||
1 u@+ nip // $0 was already verified
|
||||
|
||||
// multiSigWrapper$0 signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapper X;
|
||||
dict@+
|
||||
s>c // Message on top, signatures underneath
|
||||
|
||||
dup ."signing message: " <s csr. cr
|
||||
dup hash wallet_pk ed25519_sign_uint
|
||||
|
||||
256 B>u@+ swap 256 B>u@ swap
|
||||
<b swap 256 u, swap 256 u, b> <s
|
||||
|
||||
rot
|
||||
// Now we have (message) value dict
|
||||
|
||||
// udict! => value key dict bits
|
||||
key-id swap 4 udict! not abort"Failure adding signature!"
|
||||
|
||||
// Now we have message dict
|
||||
// multiSigWrapper$0 signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapper X;
|
||||
<b 0 1 u, swap dict, swap <s s, b>
|
||||
|
||||
// Renerate external message
|
||||
<b 68 7 u, wallet-addr addr, 0 Gram, 0 1 u, swap maybe-ref, b>
|
||||
|
||||
2 boc+>B dup Bx. cr
|
||||
output-file +".boc" tuck B>file
|
||||
."Saved new multisigned message to file " type cr
|
@ -1,47 +0,0 @@
|
||||
|
||||
#!/bin/bash -e
|
||||
# Define some helper functions
|
||||
chr() { [ "$1" -lt 256 ] || return 1; printf "\\$(printf '%03o' "$1")"; }
|
||||
ord() { LC_CTYPE=C printf '%d' "'$1"; }
|
||||
|
||||
cd tests
|
||||
|
||||
# Sign previous simple wallet query (a, b) with key c (a, b, c)
|
||||
fift -s ../sign.fif b abc c 2
|
||||
|
||||
# Update the wallet using code from wallet-code-update.fif,
|
||||
# also substituting the 10 keys with only 3 and setting k to 3
|
||||
fift -s ../wallet-update.fif pony code-update-a 1 3 3 0 a b c
|
||||
|
||||
# Sign the query using all keys separately, creating eight more boc files, each signed by two keys only (0 and 1..9)
|
||||
for f in {1..9}; do fift -s ../sign.fif code-update-a code-update-$(chr $((97+f))) $(chr $((97+f))) $f;done
|
||||
|
||||
# Merge all queries
|
||||
fift -s ../merge.fif code-update-{a..j} code-update-merge
|
||||
|
||||
# Inspect queries
|
||||
fift -s ../inspect.fif merge
|
||||
|
||||
# Finally run the generated files in the VM
|
||||
#
|
||||
# First init VM with constructor message
|
||||
# Then load first file with only two signatures by key a, b (0, 1)
|
||||
# Then load wallet code update (0)
|
||||
# Run seqno get-method
|
||||
# Run getPartialsByKeyId get-method
|
||||
# Load wallet code update with all signatures (and UPDATE CODE)
|
||||
# Run getPartialsByKeyId get-method
|
||||
# Finally load file with three signatures by keys a, b, c (0, 1, 2)
|
||||
#
|
||||
# The latter will actually send the message, since now only three signatures are required to send a message (but the wallet code update was signed by all 10)
|
||||
# This will not actually update the code, since there is no way (yet) to get a list of output actions generated by the TON VM from fift,
|
||||
# however the message **will be sent** since the minSig and keys fields in persistent storage will be updated.
|
||||
#
|
||||
fift -s ../test.fif \
|
||||
pony-create \
|
||||
b -1 \
|
||||
code-update-a -1 \
|
||||
0 85143 \
|
||||
0 113609 \
|
||||
code-update-merge -1 \
|
||||
abc -1
|
@ -1,114 +0,0 @@
|
||||
"TonUtil.fif" include
|
||||
"lib.fif" include
|
||||
|
||||
{
|
||||
."usage: " @' $0 type ." <init-message> <message> <func> [ <message2> <func2> ... ]" cr
|
||||
."Runs the function with ID <func> in the TON VM, using code and storage data from <init-message>.boc and <message>.boc as message (OR as the only integer parameter of get-method, if <message>.boc does not exist)." cr 1 halt
|
||||
} : usage
|
||||
$# 3 < ' usage if
|
||||
$1 +".boc" load-boc const init-boc
|
||||
$# 1- 2 /c const message-count
|
||||
|
||||
variable-set function function!
|
||||
variable-set code code!
|
||||
variable-set storage storage!
|
||||
variable-set retcode retcode!
|
||||
|
||||
|
||||
// c7
|
||||
// [ magic:0x076ef1ea actions:Integer msgs_sent:Integer
|
||||
// unixtime:Integer block_lt:Integer trans_lt:Integer
|
||||
// rand_seed:Integer balance_remaining:[Integer (Maybe Cell)]
|
||||
// myself:MsgAddressInt global_config:(Maybe Cell)
|
||||
// ] = SmartContractInfo;
|
||||
0x076ef1ea 0 0 now 0 0 <b 1234 32 u, b> hash 7 tuple 1 tuple const ctx
|
||||
|
||||
// message$_ {X:Type} info:CommonMsgInfo
|
||||
// init:(Maybe (Either StateInit ^StateInit))
|
||||
// body:(Either X ^X) = Message X;
|
||||
|
||||
init-boc <s
|
||||
// External message
|
||||
7 u@+ swap 68 <> { ."There seems to be an invalid header" cr } if // 1000100 => 68
|
||||
|
||||
8 i@+
|
||||
256 u@+ -rot
|
||||
."Init wallet address: " .addr cr
|
||||
|
||||
Gram@+ nip // Ignore grams
|
||||
|
||||
1 u@+ swap
|
||||
1- abort"This does not seem to be an init message"
|
||||
|
||||
1 u@+ swap
|
||||
{ ."Loading ref..." cr ref@ <s } if // Load ref to StateInit if ref
|
||||
|
||||
// _ split_depth:(Maybe (## 5)) special:(Maybe TickTock)
|
||||
// code:(Maybe ^Cell) data:(Maybe ^Cell)
|
||||
// library:(HashmapE 256 SimpleLib) = StateInit;
|
||||
|
||||
|
||||
1 u@+ swap
|
||||
{ ."Has split_depth" cr 5 u@+ nip } if // Ignore split_depth
|
||||
|
||||
1 u@+ swap
|
||||
{ ."Has TikTok" cr 2 u@+ nip } if // Ignore TikTok ;)
|
||||
|
||||
1 u@+ swap
|
||||
1- abort"No code!"
|
||||
ref@+ <s code! // Load code
|
||||
|
||||
1 u@+ swap
|
||||
1- abort"No data!"
|
||||
ref@+ storage! // Load storage
|
||||
|
||||
1 u@+ nip // Skip libs
|
||||
|
||||
1 u@+ swap
|
||||
{ ."Loading body ref..." cr ref@ } if // Load ref to body if ref
|
||||
message-contents! // Load init message
|
||||
-1 function! // External message
|
||||
|
||||
{
|
||||
."INIT: Calling " function . cr cr
|
||||
message-contents function code storage ctx runvmctx .s
|
||||
swap retcode!
|
||||
."Retcode: " retcode . cr
|
||||
dup storage!
|
||||
inspect-storage
|
||||
|
||||
retcode 0 <> abort"Exception"
|
||||
} : ponyvm
|
||||
|
||||
cr ."===================================================" cr cr
|
||||
ponyvm
|
||||
|
||||
2 { dup 1+ swap // Increase counter
|
||||
cr ."===================================================" cr cr
|
||||
$() +".boc"
|
||||
dup file-exists?
|
||||
{
|
||||
load-boc inspect // Automatically sets message-contents
|
||||
}
|
||||
{
|
||||
drop
|
||||
dup 1- $() parse-int
|
||||
// <b swap 256 u, b>
|
||||
message-contents!
|
||||
} cond
|
||||
|
||||
|
||||
dup 1+ swap // Increase counter
|
||||
$() parse-int function!
|
||||
|
||||
ponyvm
|
||||
|
||||
// Manually drop return values of functions
|
||||
function -1 <> {
|
||||
2drop
|
||||
} if
|
||||
|
||||
// rot
|
||||
// ."Signature: "
|
||||
// 64 B@ Bx.
|
||||
} message-count times
|
@ -1,53 +0,0 @@
|
||||
|
||||
#!/bin/bash -e
|
||||
# Define some helper functions
|
||||
chr() { [ "$1" -lt 256 ] || return 1; printf "\\$(printf '%03o' "$1")"; }
|
||||
ord() { LC_CTYPE=C printf '%d' "'$1"; }
|
||||
|
||||
mkdir -p tests
|
||||
cd tests
|
||||
# Create 10 public keys
|
||||
for f in {a..j}; do fift -s ../gen-pub.fif $f;done
|
||||
|
||||
# Create wallet with those 10 public keys on workchain 0, requiring all 10 signatures to send a message
|
||||
fift -s ../wallet-create.fif 0 pony 10 10 {a..j} | tee log
|
||||
|
||||
# Get wallet address
|
||||
address=$(sed '/Bounceable address [(]for later access[)]: /!d;s/.* //g' log)
|
||||
rm log
|
||||
|
||||
# Create a new wallet query signed with key a (ID 0), transferring 10 grams to the wallet itself
|
||||
fift -s ../create.fif pony a 0 $address 0 10 a
|
||||
|
||||
# Here, note the `a 0`: to save space, I have chosen to not use the entire ecdh key as key in the signature dictionary.
|
||||
# Instead, a **key ID** is used to distinguish signatures made by certain keys: this is a simple 4-bit value (instead of 256 bits!), equal to the position of the key in the `wallet-create` argument list.
|
||||
# In this case, the `a` key was the first key (`{a..j}` in Bash is shorthand for `a b c .. j`), so the ID is `0`.
|
||||
# What follows is the address, the seqno, the amount of grams and the savefile (`a`) for the query.
|
||||
|
||||
|
||||
|
||||
# Sign the query using all keys separately, creating eight more boc files, each signed by two keys only (0 and 1..9)
|
||||
for f in {1..9}; do fift -s ../sign.fif a $(chr $((97+f))) $(chr $((97+f))) $f;done
|
||||
|
||||
# Merge all queries
|
||||
fift -s ../merge.fif {a..j} merge
|
||||
|
||||
# Inspect queries
|
||||
fift -s ../inspect.fif merge
|
||||
|
||||
# Finally run the generated files in the VM
|
||||
#
|
||||
# First init VM with constructor message
|
||||
# Then load first file with only one signature by key a (0)
|
||||
# Run seqno get-method
|
||||
# Run getPartialsByKeyId get-method
|
||||
# Load file with all signatures (and send message)
|
||||
# Run getPartialsByKeyId get-method
|
||||
#
|
||||
fift -s ../test.fif \
|
||||
pony-create \
|
||||
a -1 \
|
||||
0 85143 \
|
||||
0 113609 \
|
||||
merge -1 \
|
||||
0 113609
|
@ -1,41 +0,0 @@
|
||||
#!/usr/bin/env -S fift -s
|
||||
"TonUtil.fif" include
|
||||
"lib.fif" include
|
||||
|
||||
{
|
||||
."usage: " @' $0 type ." <input> <key1> <key1-id> [<key2> <key2-id> ...]" cr
|
||||
."Verify multisig <input>.boc file with public key <keyN-id> loaded from file <keyN>.pubkey" cr 1 halt
|
||||
} : usage
|
||||
$# 3 < ' usage if
|
||||
$# 3 - 2 mod ' usage if
|
||||
|
||||
$1 +".boc" load-boc constant order
|
||||
order inspect cr
|
||||
|
||||
// multiSigWrapper$0 signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapper X;
|
||||
message-contents
|
||||
1 u@+ nip // $0 was already verified
|
||||
|
||||
// multiSigWrapper$0 signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapper X;
|
||||
dict@ const signatures
|
||||
|
||||
variable-set key key!
|
||||
variable-set key-id key-id!
|
||||
|
||||
2 { dup 1+ swap // Counter
|
||||
$() +".pubkey" load-pubkey key!
|
||||
dup 1+ swap // Increment counter
|
||||
$() parse-int key-id!
|
||||
|
||||
."Checking signature of key ID " key-id . ."... "
|
||||
key-id signatures 4 dict[] // Find signature
|
||||
not abort"Could not find signature with specified key ID!"
|
||||
|
||||
64 B@
|
||||
// udata BSig ukey – ?
|
||||
message-hash swap key
|
||||
|
||||
ed25519_chksignuu
|
||||
not abort"Invalid signature!"
|
||||
."OK!" cr
|
||||
} $# 1- 2 /c times
|
@ -1,229 +0,0 @@
|
||||
;; Multisig wallet smart contract (Daniil Gentili @danogentili <daniil@daniil.it>)
|
||||
|
||||
;; (Now unused) tuple manipulation primitives for integers
|
||||
tuple tuple_set(tuple t, int index, int value) asm(t value index) "SETINDEXVARQ";
|
||||
(tuple, ()) ~tuple_set(tuple t, int index, int value) asm(t value index) "SETINDEXVARQ";
|
||||
|
||||
;; Tuple manipulation primitives for cells
|
||||
tuple tuple_setcell(tuple t, int index, cell value) asm(t value index) "SETINDEXVARQ";
|
||||
(tuple, ()) ~tuple_setcell(tuple t, int index, cell value) asm(t value index) "SETINDEXVARQ";
|
||||
|
||||
int tuple_len(tuple t) asm "TLEN";
|
||||
|
||||
cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET";
|
||||
(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET";
|
||||
int udict_has?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT" "NIP";
|
||||
|
||||
;; Cleanup expired partial orders
|
||||
;; messages
|
||||
(cell, ()) ~collect_garbage(cell messages) {
|
||||
var hash = -1;
|
||||
do {
|
||||
(hash, var cs, int ok) = messages.udict_get_next?(256, hash);
|
||||
if (ok) {
|
||||
;; modeMessage$0 mode:uint8 body:^(Message X) = ModeMessage X;
|
||||
;; wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X;
|
||||
;; multiSigWrapperStorage$_ count:(## 4) signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X;
|
||||
;;
|
||||
;; Skip signatures, check expiry
|
||||
;;
|
||||
;; expiry <= now
|
||||
if (cs.skip_bits(4).skip_dict().preload_uint(32) <= now()) {
|
||||
messages~udict_delete?(256, hash);
|
||||
}
|
||||
}
|
||||
} until (~ ok);
|
||||
|
||||
return (messages, ());
|
||||
}
|
||||
|
||||
() store_db(int seqno, cell keys, cell messages) {
|
||||
set_data(begin_cell().store_uint(seqno, 32).store_dict(keys).store_dict(messages).end_cell());
|
||||
}
|
||||
|
||||
() recv_internal(slice message) impure {
|
||||
;; do nothing for internal messages
|
||||
}
|
||||
|
||||
;; multiSigWrapper$0 signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapper X;
|
||||
() recv_external(slice message) impure {
|
||||
;; Check if multiSigWrapper$0, multiSigInit$10 or future unsupported protocol
|
||||
int tag = message~load_uint(1);
|
||||
if (tag) {
|
||||
;; Throw if multiSigFuture$11
|
||||
throw_if(32, message~load_uint(1));
|
||||
|
||||
;; Return if multiSigInit$10
|
||||
return ();
|
||||
}
|
||||
|
||||
;; Check if is hme_empty$0 or hme_root$1
|
||||
;; Throw if empty signature list
|
||||
throw_unless(33, message.preload_uint(1));
|
||||
|
||||
var signatures = message~load_dict();
|
||||
slice message_copy = message;
|
||||
|
||||
;; wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X;
|
||||
(int expires_at, int msg_seqno) = (message_copy~load_uint(32), message_copy~load_uint(32));
|
||||
;; Message expired
|
||||
throw_if(34, expires_at <= now());
|
||||
|
||||
;; We will need the hash anyway
|
||||
int hash = slice_hash(message);
|
||||
|
||||
;; storage$_ seqno:uint32 minSigs:(## 4) keys:(HashmapE 4 PubKey) messages:(HashmapE 256 (MultiSigWrapperStorage X)) = Storage X;
|
||||
slice stored_data = get_data().begin_parse();
|
||||
(int stored_seqno, int min_sigs, var keys, var messages) = (stored_data~load_uint(32), stored_data~load_uint(4), stored_data~load_dict(), stored_data~load_dict());
|
||||
stored_data.end_parse();
|
||||
|
||||
;; This is a new message, so there will be no stored message
|
||||
var storedSignatureCount = 0;
|
||||
var storedMessageSignatures = new_dict();
|
||||
;; If new message, check and increase seqno
|
||||
if (stored_seqno == msg_seqno) {
|
||||
stored_seqno += 1;
|
||||
|
||||
;; If old message, seqno WILL be different, check if exists in unsigned messages dict
|
||||
} else {
|
||||
(message, int ok) = messages.udict_get?(256, hash);
|
||||
;; Throw if old message and doesn't exist in db
|
||||
throw_unless(35, ok);
|
||||
|
||||
;; multiSigWrapperStorage$_ count:(## 4) signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X;
|
||||
;;
|
||||
;; Load signatures
|
||||
storedSignatureCount = message~load_uint(4);
|
||||
storedMessageSignatures = message.preload_dict();
|
||||
}
|
||||
|
||||
accept_message();
|
||||
|
||||
int idx = -1;
|
||||
do {
|
||||
(idx, slice signature, int ok) = signatures.udict_get_next?(4, idx);
|
||||
if (ok) {
|
||||
(var public_key, ok) = keys.udict_get?(4, idx);
|
||||
throw_unless(36, ok);
|
||||
|
||||
var slice_copy = signature;
|
||||
throw_unless(37, check_signature(hash, slice_copy, public_key.preload_uint(256)));
|
||||
|
||||
if (~ storedMessageSignatures.udict_has?(4, idx)) {
|
||||
storedMessageSignatures~udict_set(4, idx, signature);
|
||||
storedSignatureCount += 1;
|
||||
}
|
||||
}
|
||||
} until (~ ok);
|
||||
|
||||
if (storedSignatureCount >= min_sigs) {
|
||||
if (message_copy~load_uint(1)) {
|
||||
;; Code upgrade
|
||||
;; codeMessage$1 minSigs:(## 4) keys:(HashmapE 4 PubKey) code:^Cell = ModeMessage X;
|
||||
;;
|
||||
min_sigs = message_copy~load_uint(4);
|
||||
keys = message_copy~load_dict();
|
||||
set_code(message_copy~load_ref());
|
||||
} else {
|
||||
;; Simple message
|
||||
;; modeMessage$0 mode:uint8 body:^(Message X) = ModeMessage X;
|
||||
;;
|
||||
var (mode, message) = (message_copy~load_uint(8), message_copy~load_ref());
|
||||
send_raw_message(message, mode);
|
||||
}
|
||||
message_copy.end_parse();
|
||||
messages~udict_delete?(256, hash);
|
||||
} else {
|
||||
;; modeMessage$0 mode:uint8 body:^(Message X) = ModeMessage X;
|
||||
;; wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X;
|
||||
;; multiSigWrapperStorage$_ count:(## 4) signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X;
|
||||
;;
|
||||
messages~udict_set_builder(
|
||||
256,
|
||||
hash,
|
||||
begin_cell()
|
||||
.store_uint(storedSignatureCount, 4)
|
||||
.store_dict(storedMessageSignatures)
|
||||
.store_uint(expires_at, 32)
|
||||
.store_uint(msg_seqno, 32)
|
||||
.store_slice(message_copy)
|
||||
);
|
||||
}
|
||||
messages~collect_garbage();
|
||||
|
||||
;; storage$_ seqno:uint32 minSigs:(## 4) keys:(HashmapE 4 PubKey) messages:(HashmapE 256 (MultiSigWrapperStorage X)) = Storage X;
|
||||
set_data(begin_cell().store_uint(stored_seqno, 32).store_uint(min_sigs, 4).store_dict(keys).store_dict(messages).end_cell());
|
||||
}
|
||||
|
||||
;; Get methods
|
||||
|
||||
int seqno() method_id {
|
||||
return get_data().begin_parse().preload_uint(32);
|
||||
}
|
||||
|
||||
(int, int) getKey(int id) method_id {
|
||||
(var res, int ok) = get_data().begin_parse().skip_bits(36).preload_dict().udict_get?(4, id);
|
||||
if (~ ok) {
|
||||
return (ok, 0);
|
||||
}
|
||||
return (-1, res.preload_uint(256));
|
||||
}
|
||||
(int, int) getId(int key) method_id {
|
||||
var keys = get_data().begin_parse().skip_bits(36).preload_dict();
|
||||
int idx = -1;
|
||||
int ok = 0;
|
||||
int found = 0;
|
||||
do {
|
||||
(idx, var keyRead, ok) = keys.udict_get_next?(4, idx);
|
||||
if (ok) {
|
||||
;; I could actually return here, but the funcompiler won't let me
|
||||
found = keyRead.preload_uint(256) == key;
|
||||
}
|
||||
} until ((~ ok) | found);
|
||||
if (found) {
|
||||
return (-1, idx);
|
||||
}
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
(cell) getPartials() method_id {
|
||||
return get_data().begin_parse().skip_bits(36).skip_dict().preload_dict();
|
||||
}
|
||||
(int, cell) getPartialsByKeyId(int id) method_id {
|
||||
cell messages = get_data().begin_parse().skip_bits(36).skip_dict().preload_dict();
|
||||
cell messages_found = new_dict();
|
||||
|
||||
int idx = -1;
|
||||
int ok = 0;
|
||||
int found = 0;
|
||||
do {
|
||||
(idx, slice message, ok) = messages.udict_get_next?(256, idx);
|
||||
if (ok) {
|
||||
if (message.skip_bits(4).preload_dict().udict_has?(4, id)) {
|
||||
found += 1;
|
||||
messages_found~udict_set_builder(256, idx, begin_cell().store_slice(message));
|
||||
}
|
||||
}
|
||||
} until (~ ok);
|
||||
|
||||
return (found, messages_found);
|
||||
}
|
||||
|
||||
(int, cell) getPartialByKey(int key) method_id {
|
||||
(int ok, int id) = getId(key);
|
||||
ifnot (ok) {
|
||||
return (0, new_dict());
|
||||
}
|
||||
return getPartialsByKeyId(id);
|
||||
}
|
||||
(int, cell) getPartialByHash(int hash) method_id {
|
||||
(slice message, int ok) = get_data().begin_parse().skip_bits(36).skip_dict().preload_dict().udict_get?(256, hash);
|
||||
ifnot (ok) {
|
||||
return (0, begin_cell().end_cell());
|
||||
}
|
||||
return (ok, begin_cell().store_slice(message).end_cell());
|
||||
}
|
||||
|
||||
(int, int) getMagic() method_id {
|
||||
return (420, 69);
|
||||
}
|
@ -1,380 +0,0 @@
|
||||
"Asm.fif" include
|
||||
// automatically generated from `/home/daniil/repos/contest/lib/crypto/smartcont/stdlib.fc` `./wallet-code-update.fc`
|
||||
PROGRAM{
|
||||
DECLPROC ~collect_garbage
|
||||
DECLPROC store_db
|
||||
DECLPROC recv_internal
|
||||
DECLPROC recv_external
|
||||
85143 DECLMETHOD seqno
|
||||
119659 DECLMETHOD getKey
|
||||
66593 DECLMETHOD getId
|
||||
124239 DECLMETHOD getPartials
|
||||
113609 DECLMETHOD getPartialsByKeyId
|
||||
125107 DECLMETHOD getPartialByKey
|
||||
100959 DECLMETHOD getPartialByHash
|
||||
77784 DECLMETHOD getMagic
|
||||
~collect_garbage PROC:<{
|
||||
-1 PUSHINT
|
||||
UNTIL:<{
|
||||
OVER
|
||||
8 PUSHPOW2
|
||||
DICTUGETNEXT
|
||||
NULLSWAPIFNOT
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
IF:<{
|
||||
s0 s2 XCHG
|
||||
4 PUSHINT
|
||||
SDSKIPFIRST
|
||||
SKIPDICT
|
||||
32 PLDU
|
||||
NOW
|
||||
LEQ
|
||||
IF:<{
|
||||
8 PUSHPOW2
|
||||
s1 s3 s3 PUXC2
|
||||
DICTUDEL
|
||||
DROP
|
||||
}>ELSE<{
|
||||
s0 s2 XCHG
|
||||
}>
|
||||
}>ELSE<{
|
||||
2SWAP
|
||||
DROP
|
||||
}>
|
||||
SWAP
|
||||
NOT
|
||||
s1 s2 XCHG
|
||||
}>
|
||||
DROP
|
||||
}>
|
||||
store_db PROC:<{
|
||||
NEWC
|
||||
s1 s3 XCHG
|
||||
32 STU
|
||||
STDICT
|
||||
STDICT
|
||||
ENDC
|
||||
c4 POP
|
||||
}>
|
||||
recv_internal PROC:<{
|
||||
DROP
|
||||
}>
|
||||
recv_external PROC:<{
|
||||
1 LDU
|
||||
SWAP
|
||||
IFJMP:<{
|
||||
1 LDU
|
||||
DROP
|
||||
32 THROWIF
|
||||
}>
|
||||
DUP
|
||||
1 PLDU
|
||||
33 THROWIFNOT
|
||||
LDDICT
|
||||
DUP
|
||||
32 LDU
|
||||
32 LDU
|
||||
s2 PUSH
|
||||
NOW
|
||||
LEQ
|
||||
34 THROWIF
|
||||
s0 s3 XCHG
|
||||
HASHSU
|
||||
c4 PUSH
|
||||
CTOS
|
||||
32 LDU
|
||||
4 LDU
|
||||
LDDICT
|
||||
LDDICT
|
||||
ENDS
|
||||
0 PUSHINT
|
||||
NEWDICT
|
||||
s5 s7 PUSH2
|
||||
EQUAL
|
||||
IF:<{
|
||||
s0 s5 XCHG
|
||||
INC
|
||||
}>ELSE<{
|
||||
2DROP
|
||||
s4 s0 PUSH2
|
||||
8 PUSHPOW2
|
||||
DICTUGET
|
||||
NULLSWAPIFNOT
|
||||
35 THROWIFNOT
|
||||
4 LDU
|
||||
PLDDICT
|
||||
s0 s5 XCHG
|
||||
}>
|
||||
ACCEPT
|
||||
-1 PUSHINT
|
||||
UNTIL:<{
|
||||
s11 PUSH
|
||||
4 PUSHINT
|
||||
DICTUGETNEXT
|
||||
NULLSWAPIFNOT
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
IF:<{
|
||||
DROP
|
||||
s0 s5 PUSH2
|
||||
4 PUSHINT
|
||||
DICTUGET
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
36 THROWIFNOT
|
||||
s3 s(-1) s0 PUXC2
|
||||
256 PLDU
|
||||
s11 s2 s2 PUXC2
|
||||
CHKSIGNU
|
||||
37 THROWIFNOT
|
||||
s1 s8 PUSH2
|
||||
4 PUSHINT
|
||||
DICTUGET
|
||||
NULLSWAPIFNOT
|
||||
NIP
|
||||
NOT
|
||||
IF:<{
|
||||
s1 s2 XCHG
|
||||
4 PUSHINT
|
||||
s3 s9 s9 PUXC2
|
||||
DICTUSET
|
||||
s0 s3 XCHG
|
||||
INC
|
||||
}>ELSE<{
|
||||
s0 s8 XCHG
|
||||
s1 s4 s4 XCHG3
|
||||
DROP
|
||||
}>
|
||||
}>ELSE<{
|
||||
s0 s8 XCHG
|
||||
s1 s4 s4 XCHG3
|
||||
DROP
|
||||
}>
|
||||
s0 s7 XCHG
|
||||
NOT
|
||||
s3 s7 XCHG
|
||||
}>
|
||||
DROP
|
||||
s10 POP
|
||||
s0 s3 PUSH2
|
||||
GEQ
|
||||
IF:<{
|
||||
DROP
|
||||
s3 POP
|
||||
s4 POP
|
||||
s4 POP
|
||||
s0 s4 XCHG
|
||||
1 LDU
|
||||
SWAP
|
||||
IF:<{
|
||||
s2 POP
|
||||
s2 POP
|
||||
4 LDU
|
||||
LDDICT
|
||||
LDREF
|
||||
SWAP
|
||||
SETCODE
|
||||
}>ELSE<{
|
||||
8 LDU
|
||||
LDREF
|
||||
s0 s2 XCHG
|
||||
SENDRAWMSG
|
||||
s3 s3 s0 XCHG3
|
||||
}>
|
||||
ENDS
|
||||
s2 s3 XCHG2
|
||||
8 PUSHPOW2
|
||||
DICTUDEL
|
||||
DROP
|
||||
}>ELSE<{
|
||||
NEWC
|
||||
4 STU
|
||||
s1 s4 XCHG
|
||||
STDICT
|
||||
s1 s6 XCHG
|
||||
32 STU
|
||||
s1 s4 XCHG
|
||||
32 STU
|
||||
s0 s5 XCHG2
|
||||
STSLICER
|
||||
s0 s1 s4 XCHG3
|
||||
8 PUSHPOW2
|
||||
DICTUSETB
|
||||
s1 s2 XCHG
|
||||
}>
|
||||
~collect_garbage CALLDICT
|
||||
NEWC
|
||||
s1 s4 XCHG
|
||||
32 STU
|
||||
4 STU
|
||||
STDICT
|
||||
STDICT
|
||||
ENDC
|
||||
c4 POP
|
||||
}>
|
||||
seqno PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
32 PLDU
|
||||
}>
|
||||
getKey PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
36 PUSHINT
|
||||
SDSKIPFIRST
|
||||
PLDDICT
|
||||
4 PUSHINT
|
||||
DICTUGET
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
NOT
|
||||
IFJMP:<{
|
||||
NIP
|
||||
0 PUSHINT
|
||||
}>
|
||||
DROP
|
||||
-1 PUSHINT
|
||||
SWAP
|
||||
256 PLDU
|
||||
}>
|
||||
getId PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
36 PUSHINT
|
||||
SDSKIPFIRST
|
||||
PLDDICT
|
||||
-1 PUSHINT
|
||||
0 PUSHINT
|
||||
UNTIL:<{
|
||||
s1 s2 XCPU
|
||||
4 PUSHINT
|
||||
DICTUGETNEXT
|
||||
NULLSWAPIFNOT
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
IF:<{
|
||||
s3 POP
|
||||
SWAP
|
||||
256 PLDU
|
||||
s4 PUSH
|
||||
EQUAL
|
||||
}>ELSE<{
|
||||
s1 s3 s3 XCHG3
|
||||
DROP
|
||||
}>
|
||||
s0 s2 XCHG
|
||||
NOT
|
||||
s2 PUSH
|
||||
OR
|
||||
s1 s2 XCHG
|
||||
}>
|
||||
s2 POP
|
||||
s2 POP
|
||||
IFJMP:<{
|
||||
-1 PUSHINT
|
||||
SWAP
|
||||
}>
|
||||
DROP
|
||||
0 PUSHINT
|
||||
DUP
|
||||
}>
|
||||
getPartials PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
36 PUSHINT
|
||||
SDSKIPFIRST
|
||||
SKIPDICT
|
||||
PLDDICT
|
||||
}>
|
||||
getPartialsByKeyId PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
36 PUSHINT
|
||||
SDSKIPFIRST
|
||||
SKIPDICT
|
||||
PLDDICT
|
||||
NEWDICT
|
||||
-1 PUSHINT
|
||||
0 PUSHINT
|
||||
UNTIL:<{
|
||||
s1 s3 XCPU
|
||||
8 PUSHPOW2
|
||||
DICTUGETNEXT
|
||||
NULLSWAPIFNOT
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
IF:<{
|
||||
s2 PUSH
|
||||
4 PUSHINT
|
||||
SDSKIPFIRST
|
||||
PLDDICT
|
||||
s7 s(-1) PUXC
|
||||
4 PUSHINT
|
||||
DICTUGET
|
||||
NULLSWAPIFNOT
|
||||
NIP
|
||||
IF:<{
|
||||
s0 s3 XCHG
|
||||
INC
|
||||
NEWC
|
||||
s0 s3 XCHG2
|
||||
STSLICER
|
||||
SWAP
|
||||
8 PUSHPOW2
|
||||
s1 s5 s5 PUXC2
|
||||
DICTUSETB
|
||||
}>ELSE<{
|
||||
s3 s4 s3 XCHG3
|
||||
DROP
|
||||
}>
|
||||
}>ELSE<{
|
||||
s3 s4 s3 XCHG3
|
||||
DROP
|
||||
}>
|
||||
s0 s2 XCHG
|
||||
NOT
|
||||
s2 s3 XCHG
|
||||
}>
|
||||
NIP
|
||||
s2 POP
|
||||
s2 POP
|
||||
SWAP
|
||||
}>
|
||||
getPartialByKey PROC:<{
|
||||
getId CALLDICT
|
||||
SWAP
|
||||
IFNOTJMP:<{
|
||||
DROP
|
||||
0 PUSHINT
|
||||
NEWDICT
|
||||
}>
|
||||
getPartialsByKeyId CALLDICT
|
||||
}>
|
||||
getPartialByHash PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
36 PUSHINT
|
||||
SDSKIPFIRST
|
||||
SKIPDICT
|
||||
PLDDICT
|
||||
8 PUSHPOW2
|
||||
DICTUGET
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
IFNOTJMP:<{
|
||||
2DROP
|
||||
0 PUSHINT
|
||||
NEWC
|
||||
ENDC
|
||||
}>
|
||||
NEWC
|
||||
ROT
|
||||
STSLICER
|
||||
ENDC
|
||||
}>
|
||||
getMagic PROC:<{
|
||||
420 PUSHINT
|
||||
69 PUSHINT
|
||||
}>
|
||||
}END>c
|
@ -1,227 +0,0 @@
|
||||
;; Multisig wallet smart contract (Daniil Gentili @danogentili <daniil@daniil.it>)
|
||||
|
||||
;; (Now unused) tuple manipulation primitives for integers
|
||||
tuple tuple_set(tuple t, int index, int value) asm(t value index) "SETINDEXVARQ";
|
||||
(tuple, ()) ~tuple_set(tuple t, int index, int value) asm(t value index) "SETINDEXVARQ";
|
||||
|
||||
;; Tuple manipulation primitives for cells
|
||||
tuple tuple_setcell(tuple t, int index, cell value) asm(t value index) "SETINDEXVARQ";
|
||||
(tuple, ()) ~tuple_setcell(tuple t, int index, cell value) asm(t value index) "SETINDEXVARQ";
|
||||
|
||||
int tuple_len(tuple t) asm "TLEN";
|
||||
|
||||
cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET";
|
||||
(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET";
|
||||
int udict_has?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT" "NIP";
|
||||
|
||||
;; Cleanup expired partial orders
|
||||
;; messages
|
||||
(cell, ()) ~collect_garbage(cell messages) {
|
||||
var hash = -1;
|
||||
do {
|
||||
(hash, var cs, int ok) = messages.udict_get_next?(256, hash);
|
||||
if (ok) {
|
||||
;; modeMessage$0 mode:uint8 body:^(Message X) = ModeMessage X;
|
||||
;; wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X;
|
||||
;; multiSigWrapperStorage$_ count:(## 4) signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X;
|
||||
;;
|
||||
;; Skip signatures, check expiry
|
||||
;;
|
||||
;; expiry <= now
|
||||
if (cs.skip_bits(4).skip_dict().preload_uint(32) <= now()) {
|
||||
messages~udict_delete?(256, hash);
|
||||
}
|
||||
}
|
||||
} until (~ ok);
|
||||
|
||||
return (messages, ());
|
||||
}
|
||||
|
||||
() store_db(int seqno, cell keys, cell messages) {
|
||||
set_data(begin_cell().store_uint(seqno, 32).store_dict(keys).store_dict(messages).end_cell());
|
||||
}
|
||||
|
||||
() recv_internal(slice message) impure {
|
||||
;; do nothing for internal messages
|
||||
}
|
||||
|
||||
;; multiSigWrapper$0 signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapper X;
|
||||
() recv_external(slice message) impure {
|
||||
;; Check if multiSigWrapper$0, multiSigInit$10 or future unsupported protocol
|
||||
int tag = message~load_uint(1);
|
||||
if (tag) {
|
||||
;; Throw if multiSigFuture$11
|
||||
throw_if(32, message~load_uint(1));
|
||||
|
||||
;; Accept and return if multiSigInit$10
|
||||
accept_message();
|
||||
|
||||
return ();
|
||||
}
|
||||
|
||||
;; Check if is hme_empty$0 or hme_root$1
|
||||
;; Throw if empty signature list
|
||||
throw_unless(33, message.preload_uint(1));
|
||||
|
||||
var signatures = message~load_dict();
|
||||
slice message_copy = message;
|
||||
|
||||
;; wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X;
|
||||
(int expires_at, int msg_seqno) = (message_copy~load_uint(32), message_copy~load_uint(32));
|
||||
;; Message expired
|
||||
throw_if(34, expires_at <= now());
|
||||
|
||||
;; We will need the hash anyway
|
||||
int hash = slice_hash(message);
|
||||
|
||||
;; storage$_ seqno:uint32 minSigs:(## 4) keys:(HashmapE 4 PubKey) messages:(HashmapE 256 (MultiSigWrapperStorage X)) = Storage X;
|
||||
slice stored_data = get_data().begin_parse();
|
||||
(int stored_seqno, int min_sigs, var keys, var messages) = (stored_data~load_uint(32), stored_data~load_uint(4), stored_data~load_dict(), stored_data~load_dict());
|
||||
stored_data.end_parse();
|
||||
|
||||
;; This is a new message, so there will be no stored message
|
||||
var storedSignatureCount = 0;
|
||||
var storedMessageSignatures = new_dict();
|
||||
;; If new message, check and increase seqno
|
||||
if (stored_seqno == msg_seqno) {
|
||||
stored_seqno += 1;
|
||||
|
||||
;; If old message, seqno WILL be different, check if exists in unsigned messages dict
|
||||
} else {
|
||||
(message, int ok) = messages.udict_get?(256, hash);
|
||||
;; Throw if old message and doesn't exist in db
|
||||
throw_unless(35, ok);
|
||||
|
||||
;; multiSigWrapperStorage$_ count:(## 4) signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X;
|
||||
;;
|
||||
;; Load signatures
|
||||
storedSignatureCount = message~load_uint(4);
|
||||
storedMessageSignatures = message.preload_dict();
|
||||
}
|
||||
|
||||
accept_message();
|
||||
|
||||
int idx = -1;
|
||||
do {
|
||||
(idx, slice signature, int ok) = signatures.udict_get_next?(4, idx);
|
||||
if (ok) {
|
||||
(var public_key, ok) = keys.udict_get?(4, idx);
|
||||
throw_unless(36, ok);
|
||||
|
||||
var slice_copy = signature;
|
||||
throw_unless(37, check_signature(hash, slice_copy, public_key.preload_uint(256)));
|
||||
|
||||
if (~ storedMessageSignatures.udict_has?(4, idx)) {
|
||||
storedMessageSignatures~udict_set(4, idx, signature);
|
||||
storedSignatureCount += 1;
|
||||
}
|
||||
}
|
||||
} until (~ ok);
|
||||
|
||||
if (storedSignatureCount >= min_sigs) {
|
||||
if (message_copy~load_uint(1)) {
|
||||
;; Code upgrade
|
||||
;; codeMessage$1 minSigs:(## 4) keys:(HashmapE 4 PubKey) code:^Cell = ModeMessage X;
|
||||
;;
|
||||
min_sigs = message_copy~load_uint(4);
|
||||
keys = message_copy~load_dict();
|
||||
set_code(message_copy~load_ref());
|
||||
} else {
|
||||
;; Simple message
|
||||
;; modeMessage$0 mode:uint8 body:^(Message X) = ModeMessage X;
|
||||
;;
|
||||
var (mode, message) = (message_copy~load_uint(8), message_copy~load_ref());
|
||||
send_raw_message(message, mode);
|
||||
}
|
||||
message_copy.end_parse();
|
||||
messages~udict_delete?(256, hash);
|
||||
} else {
|
||||
;; modeMessage$0 mode:uint8 body:^(Message X) = ModeMessage X;
|
||||
;; wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X;
|
||||
;; multiSigWrapperStorage$_ count:(## 4) signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X;
|
||||
;;
|
||||
messages~udict_set_builder(
|
||||
256,
|
||||
hash,
|
||||
begin_cell()
|
||||
.store_uint(storedSignatureCount, 4)
|
||||
.store_dict(storedMessageSignatures)
|
||||
.store_uint(expires_at, 32)
|
||||
.store_uint(msg_seqno, 32)
|
||||
.store_slice(message_copy)
|
||||
);
|
||||
}
|
||||
messages~collect_garbage();
|
||||
|
||||
;; storage$_ seqno:uint32 minSigs:(## 4) keys:(HashmapE 4 PubKey) messages:(HashmapE 256 (MultiSigWrapperStorage X)) = Storage X;
|
||||
set_data(begin_cell().store_uint(stored_seqno, 32).store_uint(min_sigs, 4).store_dict(keys).store_dict(messages).end_cell());
|
||||
}
|
||||
|
||||
;; Get methods
|
||||
|
||||
int seqno() method_id {
|
||||
return get_data().begin_parse().preload_uint(32);
|
||||
}
|
||||
|
||||
(int, int) getKey(int id) method_id {
|
||||
(var res, int ok) = get_data().begin_parse().skip_bits(36).preload_dict().udict_get?(4, id);
|
||||
if (~ ok) {
|
||||
return (ok, 0);
|
||||
}
|
||||
return (-1, res.preload_uint(256));
|
||||
}
|
||||
(int, int) getId(int key) method_id {
|
||||
var keys = get_data().begin_parse().skip_bits(36).preload_dict();
|
||||
int idx = -1;
|
||||
int ok = 0;
|
||||
int found = 0;
|
||||
do {
|
||||
(idx, var keyRead, ok) = keys.udict_get_next?(4, idx);
|
||||
if (ok) {
|
||||
;; I could actually return here, but the funcompiler won't let me
|
||||
found = keyRead.preload_uint(256) == key;
|
||||
}
|
||||
} until ((~ ok) | found);
|
||||
if (found) {
|
||||
return (-1, idx);
|
||||
}
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
(cell) getPartials() method_id {
|
||||
return get_data().begin_parse().skip_bits(36).skip_dict().preload_dict();
|
||||
}
|
||||
(int, cell) getPartialsByKeyId(int id) method_id {
|
||||
cell messages = get_data().begin_parse().skip_bits(36).skip_dict().preload_dict();
|
||||
cell messages_found = new_dict();
|
||||
|
||||
int idx = -1;
|
||||
int ok = 0;
|
||||
int found = 0;
|
||||
do {
|
||||
(idx, slice message, ok) = messages.udict_get_next?(256, idx);
|
||||
if (ok) {
|
||||
if (message.skip_bits(4).preload_dict().udict_has?(4, id)) {
|
||||
found += 1;
|
||||
messages_found~udict_set_builder(256, idx, begin_cell().store_slice(message));
|
||||
}
|
||||
}
|
||||
} until (~ ok);
|
||||
|
||||
return (found, messages_found);
|
||||
}
|
||||
|
||||
(int, cell) getPartialByKey(int key) method_id {
|
||||
(int ok, int id) = getId(key);
|
||||
ifnot (ok) {
|
||||
return (0, new_dict());
|
||||
}
|
||||
return getPartialsByKeyId(id);
|
||||
}
|
||||
(int, cell) getPartialByHash(int hash) method_id {
|
||||
(slice message, int ok) = get_data().begin_parse().skip_bits(36).skip_dict().preload_dict().udict_get?(256, hash);
|
||||
ifnot (ok) {
|
||||
return (0, begin_cell().end_cell());
|
||||
}
|
||||
return (ok, begin_cell().store_slice(message).end_cell());
|
||||
}
|
@ -1,375 +0,0 @@
|
||||
"Asm.fif" include
|
||||
// automatically generated from `/home/daniil/repos/contest/lib/crypto/smartcont/stdlib.fc` `./wallet-code.fc`
|
||||
PROGRAM{
|
||||
DECLPROC ~collect_garbage
|
||||
DECLPROC store_db
|
||||
DECLPROC recv_internal
|
||||
DECLPROC recv_external
|
||||
85143 DECLMETHOD seqno
|
||||
119659 DECLMETHOD getKey
|
||||
66593 DECLMETHOD getId
|
||||
124239 DECLMETHOD getPartials
|
||||
113609 DECLMETHOD getPartialsByKeyId
|
||||
125107 DECLMETHOD getPartialByKey
|
||||
100959 DECLMETHOD getPartialByHash
|
||||
~collect_garbage PROC:<{
|
||||
-1 PUSHINT
|
||||
UNTIL:<{
|
||||
OVER
|
||||
8 PUSHPOW2
|
||||
DICTUGETNEXT
|
||||
NULLSWAPIFNOT
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
IF:<{
|
||||
s0 s2 XCHG
|
||||
4 PUSHINT
|
||||
SDSKIPFIRST
|
||||
SKIPDICT
|
||||
32 PLDU
|
||||
NOW
|
||||
LEQ
|
||||
IF:<{
|
||||
8 PUSHPOW2
|
||||
s1 s3 s3 PUXC2
|
||||
DICTUDEL
|
||||
DROP
|
||||
}>ELSE<{
|
||||
s0 s2 XCHG
|
||||
}>
|
||||
}>ELSE<{
|
||||
2SWAP
|
||||
DROP
|
||||
}>
|
||||
SWAP
|
||||
NOT
|
||||
s1 s2 XCHG
|
||||
}>
|
||||
DROP
|
||||
}>
|
||||
store_db PROC:<{
|
||||
NEWC
|
||||
s1 s3 XCHG
|
||||
32 STU
|
||||
STDICT
|
||||
STDICT
|
||||
ENDC
|
||||
c4 POP
|
||||
}>
|
||||
recv_internal PROC:<{
|
||||
DROP
|
||||
}>
|
||||
recv_external PROC:<{
|
||||
1 LDU
|
||||
SWAP
|
||||
IFJMP:<{
|
||||
1 LDU
|
||||
DROP
|
||||
32 THROWIF
|
||||
}>
|
||||
DUP
|
||||
1 PLDU
|
||||
33 THROWIFNOT
|
||||
LDDICT
|
||||
DUP
|
||||
32 LDU
|
||||
32 LDU
|
||||
s2 PUSH
|
||||
NOW
|
||||
LEQ
|
||||
34 THROWIF
|
||||
s0 s3 XCHG
|
||||
HASHSU
|
||||
c4 PUSH
|
||||
CTOS
|
||||
32 LDU
|
||||
4 LDU
|
||||
LDDICT
|
||||
LDDICT
|
||||
ENDS
|
||||
0 PUSHINT
|
||||
NEWDICT
|
||||
s5 s7 PUSH2
|
||||
EQUAL
|
||||
IF:<{
|
||||
s0 s5 XCHG
|
||||
INC
|
||||
}>ELSE<{
|
||||
2DROP
|
||||
s4 s0 PUSH2
|
||||
8 PUSHPOW2
|
||||
DICTUGET
|
||||
NULLSWAPIFNOT
|
||||
35 THROWIFNOT
|
||||
4 LDU
|
||||
PLDDICT
|
||||
s0 s5 XCHG
|
||||
}>
|
||||
ACCEPT
|
||||
-1 PUSHINT
|
||||
UNTIL:<{
|
||||
s11 PUSH
|
||||
4 PUSHINT
|
||||
DICTUGETNEXT
|
||||
NULLSWAPIFNOT
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
IF:<{
|
||||
DROP
|
||||
s0 s5 PUSH2
|
||||
4 PUSHINT
|
||||
DICTUGET
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
36 THROWIFNOT
|
||||
s3 s(-1) s0 PUXC2
|
||||
256 PLDU
|
||||
s11 s2 s2 PUXC2
|
||||
CHKSIGNU
|
||||
37 THROWIFNOT
|
||||
s1 s8 PUSH2
|
||||
4 PUSHINT
|
||||
DICTUGET
|
||||
NULLSWAPIFNOT
|
||||
NIP
|
||||
NOT
|
||||
IF:<{
|
||||
s1 s2 XCHG
|
||||
4 PUSHINT
|
||||
s3 s9 s9 PUXC2
|
||||
DICTUSET
|
||||
s0 s3 XCHG
|
||||
INC
|
||||
}>ELSE<{
|
||||
s0 s8 XCHG
|
||||
s1 s4 s4 XCHG3
|
||||
DROP
|
||||
}>
|
||||
}>ELSE<{
|
||||
s0 s8 XCHG
|
||||
s1 s4 s4 XCHG3
|
||||
DROP
|
||||
}>
|
||||
s0 s7 XCHG
|
||||
NOT
|
||||
s3 s7 XCHG
|
||||
}>
|
||||
DROP
|
||||
s10 POP
|
||||
s0 s3 PUSH2
|
||||
GEQ
|
||||
IF:<{
|
||||
DROP
|
||||
s3 POP
|
||||
s4 POP
|
||||
s4 POP
|
||||
s0 s4 XCHG
|
||||
1 LDU
|
||||
SWAP
|
||||
IF:<{
|
||||
s2 POP
|
||||
s2 POP
|
||||
4 LDU
|
||||
LDDICT
|
||||
LDREF
|
||||
SWAP
|
||||
SETCODE
|
||||
}>ELSE<{
|
||||
8 LDU
|
||||
LDREF
|
||||
s0 s2 XCHG
|
||||
SENDRAWMSG
|
||||
s3 s3 s0 XCHG3
|
||||
}>
|
||||
ENDS
|
||||
s2 s3 XCHG2
|
||||
8 PUSHPOW2
|
||||
DICTUDEL
|
||||
DROP
|
||||
}>ELSE<{
|
||||
NEWC
|
||||
4 STU
|
||||
s1 s4 XCHG
|
||||
STDICT
|
||||
s1 s6 XCHG
|
||||
32 STU
|
||||
s1 s4 XCHG
|
||||
32 STU
|
||||
s0 s5 XCHG2
|
||||
STSLICER
|
||||
s0 s1 s4 XCHG3
|
||||
8 PUSHPOW2
|
||||
DICTUSETB
|
||||
s1 s2 XCHG
|
||||
}>
|
||||
~collect_garbage CALLDICT
|
||||
NEWC
|
||||
s1 s4 XCHG
|
||||
32 STU
|
||||
4 STU
|
||||
STDICT
|
||||
STDICT
|
||||
ENDC
|
||||
c4 POP
|
||||
}>
|
||||
seqno PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
32 PLDU
|
||||
}>
|
||||
getKey PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
36 PUSHINT
|
||||
SDSKIPFIRST
|
||||
PLDDICT
|
||||
4 PUSHINT
|
||||
DICTUGET
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
NOT
|
||||
IFJMP:<{
|
||||
NIP
|
||||
0 PUSHINT
|
||||
}>
|
||||
DROP
|
||||
-1 PUSHINT
|
||||
SWAP
|
||||
256 PLDU
|
||||
}>
|
||||
getId PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
36 PUSHINT
|
||||
SDSKIPFIRST
|
||||
PLDDICT
|
||||
-1 PUSHINT
|
||||
0 PUSHINT
|
||||
UNTIL:<{
|
||||
s1 s2 XCPU
|
||||
4 PUSHINT
|
||||
DICTUGETNEXT
|
||||
NULLSWAPIFNOT
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
IF:<{
|
||||
s3 POP
|
||||
SWAP
|
||||
256 PLDU
|
||||
s4 PUSH
|
||||
EQUAL
|
||||
}>ELSE<{
|
||||
s1 s3 s3 XCHG3
|
||||
DROP
|
||||
}>
|
||||
s0 s2 XCHG
|
||||
NOT
|
||||
s2 PUSH
|
||||
OR
|
||||
s1 s2 XCHG
|
||||
}>
|
||||
s2 POP
|
||||
s2 POP
|
||||
IFJMP:<{
|
||||
-1 PUSHINT
|
||||
SWAP
|
||||
}>
|
||||
DROP
|
||||
0 PUSHINT
|
||||
DUP
|
||||
}>
|
||||
getPartials PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
36 PUSHINT
|
||||
SDSKIPFIRST
|
||||
SKIPDICT
|
||||
PLDDICT
|
||||
}>
|
||||
getPartialsByKeyId PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
36 PUSHINT
|
||||
SDSKIPFIRST
|
||||
SKIPDICT
|
||||
PLDDICT
|
||||
NEWDICT
|
||||
-1 PUSHINT
|
||||
0 PUSHINT
|
||||
UNTIL:<{
|
||||
s1 s3 XCPU
|
||||
8 PUSHPOW2
|
||||
DICTUGETNEXT
|
||||
NULLSWAPIFNOT
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
IF:<{
|
||||
s2 PUSH
|
||||
4 PUSHINT
|
||||
SDSKIPFIRST
|
||||
PLDDICT
|
||||
s7 s(-1) PUXC
|
||||
4 PUSHINT
|
||||
DICTUGET
|
||||
NULLSWAPIFNOT
|
||||
NIP
|
||||
IF:<{
|
||||
s0 s3 XCHG
|
||||
INC
|
||||
NEWC
|
||||
s0 s3 XCHG2
|
||||
STSLICER
|
||||
SWAP
|
||||
8 PUSHPOW2
|
||||
s1 s5 s5 PUXC2
|
||||
DICTUSETB
|
||||
}>ELSE<{
|
||||
s3 s4 s3 XCHG3
|
||||
DROP
|
||||
}>
|
||||
}>ELSE<{
|
||||
s3 s4 s3 XCHG3
|
||||
DROP
|
||||
}>
|
||||
s0 s2 XCHG
|
||||
NOT
|
||||
s2 s3 XCHG
|
||||
}>
|
||||
NIP
|
||||
s2 POP
|
||||
s2 POP
|
||||
SWAP
|
||||
}>
|
||||
getPartialByKey PROC:<{
|
||||
getId CALLDICT
|
||||
SWAP
|
||||
IFNOTJMP:<{
|
||||
DROP
|
||||
0 PUSHINT
|
||||
NEWDICT
|
||||
}>
|
||||
getPartialsByKeyId CALLDICT
|
||||
}>
|
||||
getPartialByHash PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
36 PUSHINT
|
||||
SDSKIPFIRST
|
||||
SKIPDICT
|
||||
PLDDICT
|
||||
8 PUSHPOW2
|
||||
DICTUGET
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
IFNOTJMP:<{
|
||||
2DROP
|
||||
0 PUSHINT
|
||||
NEWC
|
||||
ENDC
|
||||
}>
|
||||
NEWC
|
||||
ROT
|
||||
STSLICER
|
||||
ENDC
|
||||
}>
|
||||
}END>c
|
@ -1,102 +0,0 @@
|
||||
"TonUtil.fif" include
|
||||
"lib.fif" include
|
||||
|
||||
{ ."usage: " @' $0 type ." <workchain-id> <wallet-name> <n> <k> <privkey1> [<pubkey2> ...]" cr cr
|
||||
."Creates a new multisignature wallet in specified workchain composed of <n> (1-10) keys." cr
|
||||
."The first of the keys must be a private key (pre-existing or not), used to generate the wallet; the rest MUST be public keys." cr
|
||||
."Create or generate public key files from private keys using gen-pub.fif privkey" cr cr
|
||||
."Min <k> (1-10) signatures required to send an order; load <n> pre-existing public keys from files <key1...n>." cr
|
||||
1 halt
|
||||
} : usage
|
||||
$# 5 < ' usage if
|
||||
|
||||
$1 parse-workchain-id =: wc // set workchain id from command line argument
|
||||
$2 constant file-base
|
||||
$3 (number) 1 <> abort"<n> must be a number!" constant n
|
||||
$4 (number) 1 <> abort"<k> must be a number!" constant k
|
||||
|
||||
n 1 < n 10 > or abort"<n> must be between 1 and 10"
|
||||
k 1 < k 10 > or abort"<k> must be between 1 and 10"
|
||||
k n <= not abort"<k> must smaller than or equal to <n>"
|
||||
|
||||
$# 4 n + < abort"Not enough keys were provided in args!"
|
||||
|
||||
$5 +".pk" load-generate-keypair const privkey 256 B>u@
|
||||
|
||||
6 { dup $() +".pubkey" load-pubkey swap 1+ } n 1- times drop
|
||||
n tuple constant keys
|
||||
|
||||
|
||||
cr
|
||||
."Creating new multisignature wallet in workchain " wc .
|
||||
."with n=" n .
|
||||
."k=" k . ."..." cr cr
|
||||
|
||||
// idict! (v x s n – s0 −1 or s 0), adds a new value v (represented
|
||||
// by a Slice) with key given by signed big-endian n-bit integer x into
|
||||
// dictionary s with n-bit keys, and returns the new dictionary s0 and −1
|
||||
// on success. Otherwise the unchanged dictionary s and 0 are returned.
|
||||
|
||||
// Create dictionaries with keys and messages
|
||||
// Extract keys
|
||||
keys explode
|
||||
|
||||
dup 1- // Create counter
|
||||
dictnew swap // ...and dict (swap the two)
|
||||
rot // Put length on top for times
|
||||
{ dup 1- swap // Decrement counter
|
||||
3 roll // Get n-th value v (val dict ncount curcount)
|
||||
<b swap 256 u, // Create builder bval
|
||||
swap // Get x (dict ncount curcount bval)
|
||||
3 roll // Get dictionary s (dict ncount bval curcount)
|
||||
4 // Get n (ncount bval curcount dict) 4
|
||||
b>udict!
|
||||
not abort"Failure storing dictionary value!"
|
||||
|
||||
swap
|
||||
} swap times drop const keys-dict
|
||||
.s
|
||||
// code
|
||||
"wallet-code.fif" include
|
||||
// data
|
||||
// storage$_ seqno:uint32 minSigs:(## 4) keys:(HashmapE 4 PubKey) messages:(HashmapE 256 (MultiSigWrapperStorage X))
|
||||
// { minSigs > 0 } { n >= minSigs } { n <= 10 } { minSigs <= 10 } = Storage X;
|
||||
<b 0 32 u,
|
||||
k 4 u,
|
||||
keys-dict dict,
|
||||
dictnew dict,
|
||||
b>
|
||||
// no libraries
|
||||
null
|
||||
// create StateInit
|
||||
// _ split_depth:(Maybe (## 5)) special:(Maybe TickTock)
|
||||
// code:(Maybe ^Cell) data:(Maybe ^Cell)
|
||||
// library:(HashmapE 256 SimpleLib) = StateInit;
|
||||
// split_depth 0 special 0 code 1 data 1
|
||||
//
|
||||
<b b{0011} s, 3 roll ref, rot ref, swap dict, b>
|
||||
|
||||
dup ."StateInit: " <s csr. cr
|
||||
dup hash wc swap 2dup 2constant wallet-addr
|
||||
."new wallet address = " 2dup .addr cr
|
||||
2dup file-base +".addr" save-address-verbose
|
||||
."Non-bounceable address (for init): " 2dup 7 .Addr cr
|
||||
."Bounceable address (for later access): " 6 .Addr cr
|
||||
|
||||
// multiSigInit$10 = MultiSigWrapper X;
|
||||
<b 2 2 u, b>
|
||||
// dup ."signing message: " <s csr. cr
|
||||
// dup hash privkey ed25519_sign_uint
|
||||
|
||||
// Put StateInit on top
|
||||
swap
|
||||
|
||||
// message$_ {X:Type} info:CommonMsgInfo
|
||||
// init:(Maybe (Either StateInit ^StateInit))
|
||||
// body:(Either X ^X) = Message X;
|
||||
|
||||
<b b{1000100} s, wallet-addr addr, 0 Gram, 1 1 u, swap maybe-ref, b{0} s, swap <s s, b>
|
||||
dup ."External message for initialization is " <s csr. cr
|
||||
2 boc+>B dup Bx. cr
|
||||
file-base +"-create.boc" tuck B>file
|
||||
."(Saved wallet creating query to file " type .")" cr
|
@ -1,128 +0,0 @@
|
||||
"TonUtil.fif" include
|
||||
"lib.fif" include
|
||||
|
||||
{ ."usage: " @' $0 type ." <wallet-name> <wallet-update> <seqno> <n> <k> <key-id> <privkey1> [<pubkey2> ...]" cr cr
|
||||
."Updates an existing multisignature wallet." cr
|
||||
."The first of the keys must be a private key (with ID <key-id>), used to sign the wallet update request saved to <wallet-update>.boc; the rest MUST be public keys." cr
|
||||
."Create or generate public key files from private keys using gen-pub.fif privkey" cr cr
|
||||
."Min <k> (1-10) signatures required to send an order; load <n> pre-existing public keys from files <key1...n>." cr
|
||||
1 halt
|
||||
} : usage
|
||||
$# 6 < ' usage if
|
||||
|
||||
$1 constant file-base
|
||||
$2 constant update-file
|
||||
$3 parse-int constant msg-seqno
|
||||
$4 (number) 1 <> abort"<n> must be a number!" constant n
|
||||
$5 (number) 1 <> abort"<k> must be a number!" constant k
|
||||
$6 (number) 1 <> abort"<k> must be a number!" constant key-id
|
||||
|
||||
|
||||
file-base +".addr" load-address
|
||||
2dup 2constant wallet-addr
|
||||
."Source wallet address = " 2dup .addr cr 6 .Addr cr
|
||||
|
||||
3 constant send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors
|
||||
3 86400 * constant timeout // external message expires in 3 days
|
||||
|
||||
n 1 < n 10 > or abort"<n> must be between 1 and 10"
|
||||
k 1 < k 10 > or abort"<k> must be between 1 and 10"
|
||||
k n <= not abort"<k> must smaller than or equal to <n>"
|
||||
|
||||
$# 6 n + < abort"Not enough keys were provided in args!"
|
||||
|
||||
$7 +".pk" load-generate-keypair const privkey 256 B>u@
|
||||
|
||||
8 { dup $() +".pubkey" load-pubkey swap 1+ } n 1- times drop
|
||||
n tuple constant keys
|
||||
|
||||
|
||||
cr
|
||||
."Updating pre-existing multisignature wallet with n=" n .
|
||||
."k=" k . ."..." cr cr
|
||||
|
||||
// idict! (v x s n – s0 −1 or s 0), adds a new value v (represented
|
||||
// by a Slice) with key given by signed big-endian n-bit integer x into
|
||||
// dictionary s with n-bit keys, and returns the new dictionary s0 and −1
|
||||
// on success. Otherwise the unchanged dictionary s and 0 are returned.
|
||||
|
||||
// Create dictionaries with keys and messages
|
||||
// Extract keys
|
||||
keys explode
|
||||
|
||||
dup 1- // Create counter
|
||||
dictnew swap // ...and dict (swap the two)
|
||||
rot // Put length on top for times
|
||||
{ dup 1- swap // Decrement counter
|
||||
3 roll // Get n-th value v (val dict ncount curcount)
|
||||
<b swap 256 u, // Create builder bval
|
||||
swap // Get x (dict ncount curcount bval)
|
||||
3 roll // Get dictionary s (dict ncount bval curcount)
|
||||
4 // Get n (ncount bval curcount dict) 4
|
||||
b>udict!
|
||||
not abort"Failure storing dictionary value!"
|
||||
|
||||
swap
|
||||
} swap times drop const keys-dict
|
||||
|
||||
// code
|
||||
"wallet-code-update.fif" include
|
||||
// Create code message
|
||||
// codeMessage$1 minSigs:(## 4) keys:(HashmapE 4 PubKey) code:^Cell = ModeMessage X;
|
||||
<b 1 1 u,
|
||||
k 4 u,
|
||||
keys-dict dict,
|
||||
swap ref,
|
||||
b>
|
||||
// create wrapper message
|
||||
// wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X;
|
||||
<b now timeout + 32 u, msg-seqno 32 u, swap <s s, b>
|
||||
|
||||
dup ."signing message: " <s csr. cr
|
||||
dup hashu
|
||||
dup ."Hash: " x. cr cr
|
||||
privkey ed25519_sign_uint
|
||||
|
||||
// signature$_ R:bits256 s:bits256 = Signature;
|
||||
256 B>u@+ swap 256 B>u@ swap
|
||||
<b swap 256 u, swap 256 u, b>
|
||||
|
||||
// key ID => signature
|
||||
// multiSigWrapper$0 signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapper X;
|
||||
<b
|
||||
0 1 u, // $0
|
||||
swap <s key-id dictnew 4 udict! // Create and populate dictionary
|
||||
not abort"Failure inserting signature!"
|
||||
|
||||
dict, // signatures
|
||||
swap <s s, // message
|
||||
b>
|
||||
|
||||
|
||||
//
|
||||
// addr_none$00 = MsgAddressExt;
|
||||
// addr_extern$01 len:(## 9) external_address:(bits len)
|
||||
// = MsgAddressExt;
|
||||
// anycast_info$_ depth:(#<= 30) { depth >= 1 }
|
||||
// rewrite_pfx:(bits depth) = Anycast;
|
||||
// addr_std$10 anycast:(Maybe Anycast)
|
||||
// workchain_id:int8 address:bits256 = MsgAddressInt;
|
||||
// addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9)
|
||||
// workchain_id:int32 address:(bits addr_len) = MsgAddressInt;
|
||||
//
|
||||
// ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt
|
||||
// import_fee:Grams = CommonMsgInfo;
|
||||
//
|
||||
//
|
||||
// message$_ {X:Type} info:CommonMsgInfo
|
||||
// init:(Maybe (Either StateInit ^StateInit))
|
||||
// body:(Either X ^X) = Message X;
|
||||
//
|
||||
<b b{1000100} s, wallet-addr addr, 0 Gram, 0 1 u,
|
||||
swap maybe-ref, b>
|
||||
|
||||
dup ."resulting external message: " <s csr. cr
|
||||
2 boc+>B dup Bx. cr
|
||||
update-file +".boc" tuck B>file
|
||||
."Query expires in " timeout . ."seconds" cr
|
||||
."(Saved to file " type .")" cr
|
Loading…
Reference in New Issue
Block a user