-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
<!-- enter the gh issue after hash --> - [X] follows contribution [guide](https://github.com/keep-starknet-strange/shinigami/blob/main/CONTRIBUTING.md) - [X] code change includes tests <!-- PR description below --> - Add module to convert integer to Bitcoin 'sign-magnitude' representation in a ByteArray. - Add conversion tests - Edit some tests of opcode using pop_int() --------- Co-authored-by: j1mbo64 <j1mbo64@none.dev> Co-authored-by: Brandon Roberts <brandonjroberts22@gmail.com>
- Loading branch information
1 parent
b7a6b29
commit 02839b2
Showing
7 changed files
with
207 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,13 @@ | ||
## How to add an opcode to "Shinigami" | ||
|
||
1. Understand how the opcode works by looking at documentation: | ||
- [Bitcoin Script Wiki](https://en.bitcoin.it/wiki/Script) | ||
- [Reference implementation in btcd](https://github.com/btcsuite/btcd/blob/b161cd6a199b4e35acec66afc5aad221f05fe1e3/txscript/opcode.go#L312) | ||
- In btcd find the function that matches the last element in `opcodeArray` for the specified opcode, that will be the reference implementation. | ||
2. Add the Opcode to `src/opcodes/opcodes.cairo` | ||
- Add the Opcode byte const like `pub const OP_ADD: u8 = 147;` | ||
- Create the function implementing the opcode like `fn opcode_add(ref engine: Engine) {` | ||
- Add the function to the `execute` method like `147 => opcode_add(ref engine),` | ||
3. Add the Opcode to the compiler dict at `src/compiler.cairo` like `opcodes.insert('OP_ADD', Opcode::OP_ADD);`. | ||
4. Create a test for your opcode at `src/opcodes/tests/test_opcodes.cairo` and ensure all the logic works as expected. | ||
5. Create a PR, ensure CI passes, and await review. | ||
* [Bitcoin Script Wiki](https://en.bitcoin.it/wiki/Script) | ||
* [Reference implementation in btcd](https://github.com/btcsuite/btcd/blob/b161cd6a199b4e35acec66afc5aad221f05fe1e3/txscript/opcode.go#L312) | ||
* In btcd find the function that matches the last element in `opcodeArray` for the specified opcode, that will be the reference implementation. | ||
1. Add the Opcode to `src/opcodes/opcodes.cairo`. | ||
* Add the Opcode byte const like `pub const OP_ADD: u8 = 147;` | ||
* Create the function implementing the opcode like `fn opcode_add(ref engine: Engine) {` | ||
* Add the function to the `execute` method like `147 => opcode_add(ref engine),` | ||
1. Add the Opcode to the compiler dict at `src/compiler.cairo` like `opcodes.insert('OP_ADD', Opcode::OP_ADD);`. | ||
1. Create a test for your opcode at `src/opcodes/tests/test_opcodes.cairo` and ensure all the logic works as expected. | ||
1. Create a PR, ensure CI passes, and await review. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// Wrapper around Bitcoin Script 'sign-magnitude' 4 byte integer. | ||
pub mod ScriptNum { | ||
const BYTESHIFT: i64 = 256; | ||
const MAX_BYTE_LEN: usize = 4; | ||
|
||
// Wrap i64 with a maximum size of 4 bytes. Can result in 5 byte array. | ||
pub fn wrap(mut input: i64) -> ByteArray { | ||
if input == 0 { | ||
return ""; | ||
} | ||
|
||
let mut result: ByteArray = Default::default(); | ||
let is_negative = { | ||
if input < 0 { | ||
input *= -1; | ||
true | ||
} else { | ||
false | ||
} | ||
}; | ||
// Unwrap cannot fail because input is set to positive above. | ||
let unsigned: u64 = input.try_into().unwrap(); | ||
let bytes_len: usize = integer_bytes_len(unsigned.into()); | ||
if bytes_len > MAX_BYTE_LEN { | ||
panic!("scriptnum(wrap): number more than {} bytes long", MAX_BYTE_LEN); | ||
} | ||
result.append_word_rev(unsigned.into(), bytes_len - 1); | ||
// Compute 'sign-magnitude' byte. | ||
let sign_byte: u8 = get_last_byte_of_uint(unsigned); | ||
if is_negative { | ||
if (sign_byte > 127) { | ||
result.append_byte(sign_byte); | ||
result.append_byte(128); | ||
} else { | ||
result.append_byte(sign_byte + 128); | ||
} | ||
} else { | ||
if (sign_byte > 127) { | ||
result.append_byte(sign_byte); | ||
result.append_byte(0); | ||
} else { | ||
result.append_byte(sign_byte); | ||
} | ||
} | ||
result | ||
} | ||
|
||
// Unwrap sign-magnitude encoded ByteArray into a 4 byte int maximum. | ||
pub fn unwrap(input: ByteArray) -> i64 { | ||
let mut result: i64 = 0; | ||
let mut i: u32 = 0; | ||
let mut multiplier: i64 = 1; | ||
if input.len() == 0 { | ||
return 0; | ||
} | ||
let snap_input = @input; | ||
while i < snap_input.len() | ||
- 1 { | ||
result += snap_input.at(i).unwrap().into() * multiplier; | ||
multiplier *= BYTESHIFT; | ||
i += 1; | ||
}; | ||
// Recover value and sign from 'sign-magnitude' byte. | ||
let sign_byte: i64 = input.at(i).unwrap().into(); | ||
if sign_byte >= 128 { | ||
result = (multiplier * (sign_byte - 128) * -1) - result; | ||
} else { | ||
result += sign_byte * multiplier; | ||
} | ||
if integer_bytes_len(result.into()) > MAX_BYTE_LEN { | ||
panic!("scriptnum(unwrap): number more than {} bytes long", MAX_BYTE_LEN); | ||
} | ||
result | ||
} | ||
|
||
// Return the minimal number of byte to represent 'value'. | ||
fn integer_bytes_len(mut value: i128) -> usize { | ||
if value < 0 { | ||
value *= -1; | ||
} | ||
let mut power_byte = BYTESHIFT.try_into().unwrap(); | ||
let mut bytes_len: usize = 1; | ||
while value >= power_byte { | ||
bytes_len += 1; | ||
power_byte *= 256; | ||
}; | ||
bytes_len | ||
} | ||
|
||
// Return the value of the last byte of 'value'. | ||
fn get_last_byte_of_uint(mut value: u64) -> u8 { | ||
let byteshift = BYTESHIFT.try_into().unwrap(); | ||
while value > byteshift { | ||
value = value / byteshift; | ||
}; | ||
value.try_into().unwrap() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
use core::byte_array::ByteArray; | ||
use shinigami::scriptnum::ScriptNum; | ||
#[test] | ||
fn test_scriptnum_wrap_unwrap() { | ||
let mut int = 0; | ||
let mut returned_int = ScriptNum::unwrap(ScriptNum::wrap(int)); | ||
assert!(int == returned_int, "Wrap/unwrap 0 not equal"); | ||
|
||
int = 1; | ||
returned_int = ScriptNum::unwrap(ScriptNum::wrap(int)); | ||
assert!(int == returned_int, "Wrap/unwrap 1 not equal"); | ||
|
||
int = -1; | ||
returned_int = ScriptNum::unwrap(ScriptNum::wrap(int)); | ||
assert!(int == returned_int, "Wrap/unwrap -1 not equal"); | ||
|
||
int = 32767; | ||
returned_int = ScriptNum::unwrap(ScriptNum::wrap(int)); | ||
assert!(int == returned_int, "Wrap/unwrap 32767 not equal"); | ||
|
||
int = -452354; | ||
returned_int = ScriptNum::unwrap(ScriptNum::wrap(int)); | ||
assert!(int == returned_int, "Wrap/unwrap 32767 not equal"); | ||
|
||
int = 4294967295; // 0xFFFFFFFF | ||
returned_int = ScriptNum::unwrap(ScriptNum::wrap(int)); | ||
assert!(int == returned_int, "Wrap/unwrap 4294967295 not equal"); | ||
} | ||
|
||
#[test] | ||
fn test_scriptnum_bytes_wrap() { | ||
let mut bytes: ByteArray = Default::default(); | ||
bytes.append_byte(42); // 0x2A | ||
let mut returned_int = ScriptNum::unwrap(bytes); | ||
assert!(returned_int == 42, "Unwrap 0x2A not equal to 42"); | ||
|
||
let mut bytes: ByteArray = ""; | ||
returned_int = ScriptNum::unwrap(bytes); | ||
assert!(returned_int == 0, "Unwrap empty bytes not equal to 0"); | ||
|
||
let mut bytes: ByteArray = Default::default(); | ||
bytes.append_byte(129); // 0x81 | ||
bytes.append_byte(128); // 0x80 | ||
returned_int = ScriptNum::unwrap(bytes); | ||
assert!(returned_int == -129, "Unwrap 0x8180 not equal to -129"); | ||
|
||
let mut bytes: ByteArray = Default::default(); | ||
bytes.append_byte(255); // 0xFF | ||
bytes.append_byte(127); // 0x7F | ||
returned_int = ScriptNum::unwrap(bytes); | ||
assert!(returned_int == 32767, "0xFF7F not equal to 32767"); | ||
|
||
let mut bytes: ByteArray = Default::default(); | ||
bytes.append_byte(0); // 0x00 | ||
bytes.append_byte(128); // 0x80 | ||
bytes.append_byte(128); // 0x80 | ||
returned_int = ScriptNum::unwrap(bytes); | ||
assert!(returned_int == -32768, "0x008080 not equal to -32768"); | ||
} | ||
|
||
#[test] | ||
#[should_panic] | ||
fn test_scriptnum_too_big_unwrap_panic() { | ||
let mut bytes: ByteArray = Default::default(); | ||
bytes.append_word_rev(4294967295 + 1, 5); // 0xFFFFFFFF + 1 | ||
ScriptNum::unwrap(bytes); | ||
} | ||
|
||
#[test] | ||
#[should_panic] | ||
fn test_scriptnum_too_big_wrap_panic() { | ||
let value: i64 = 4294967295 + 1; // 0xFFFFFFFF + 1 | ||
ScriptNum::wrap(value); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.