-
Notifications
You must be signed in to change notification settings - Fork 471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature] Add arithmetic types, plaintext, and conversion of plaintext & records to JS objects #944
base: mainnet
Are you sure you want to change the base?
[Feature] Add arithmetic types, plaintext, and conversion of plaintext & records to JS objects #944
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's various small nits that need to be fixed, otherwise it looks good to me.
wasm/src/ledger/transaction.rs
Outdated
#[wasm_bindgen(js_name = toBytesLe)] | ||
pub fn to_bytes_le(&self) -> Result<Uint8Array, String> { | ||
let bytes = self.0.to_bytes_le().map_err(|e| e.to_string())?; | ||
let array = Uint8Array::new_with_length(bytes.len() as u32); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should instead use Uint8Array::from
, which is simpler and faster.
wasm/src/ledger/transaction.rs
Outdated
/// | ||
/// @returns {Array<RecordPlaintext>} Array of record plaintext objects | ||
pub fn owned_records(&self, view_key: &ViewKey) -> Array { | ||
let array = Array::new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should instead use iterators and collect
:
self.0.records()
.into_iter()
.filter_map(|(_, record_ciphertext)| {
...
})
.collect()
wasm/src/ledger/transaction.rs
Outdated
/// | ||
/// @returns {Array<{commitment: Field, record: RecordCiphertext}>} Array of record ciphertext objects | ||
pub fn records(&self) -> Array { | ||
let array = Array::new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, this should use map
+ collect
.
wasm/src/ledger/transaction.rs
Outdated
pub fn records(&self) -> Array { | ||
let array = Array::new(); | ||
self.0.records().for_each(|(commitment, record_ciphertext)| { | ||
let object = Object::new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use the object!
utility macro.
wasm/src/ledger/transaction.rs
Outdated
/// @returns {Object} Transaction summary | ||
pub fn summary(&self, convert_to_js: bool) -> Result<Object, String> { | ||
// Create a transaction object. | ||
let transaction = Object::new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, it would be nice to use the object!
utility macro.
wasm/src/programs/data/plaintext.rs
Outdated
#[wasm_bindgen(js_name = "toBytesLe")] | ||
pub fn to_bytes_le(&self) -> Result<Uint8Array, String> { | ||
let rust_bytes = self.0.to_bytes_le().map_err(|e| e.to_string())?; | ||
let array = Uint8Array::new_with_length(rust_bytes.len() as u32); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use Uint8Array::from
.
} | ||
|
||
/// Invert the field element. | ||
pub fn inverse(&self) -> Field { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be called negate
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Negate isn't the right terminology in Field Theory given there's no general concept of negative
as there is in the real or complex fields. In field theory inverse
has very specific meaning and it's the corresponding member of the field that when added together produces the additive identity element (i.e. 0). If you look in SnarkVM Field traits, inverse
is the terminology used for this as it matches the formal definitions in theory, so this should stay inverse
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmm, in that case I don't think it should be using the -
operator... since -
should have the meaning of "negate", not "invert".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're generally used to a negation
being defined for signed
numeric types, but theoretically it is
creating the inverse element to the positive number, so it is indeed referring to an inversion
operation.
A finite field is different however in that there are no negative numbers, and thus no signed bit to denote the sign. The confusion here stems from the fact that in unsigned integers, there's no inverse element (and thus it's not a field, ring, or even a group by definition) but since a finite field is indeed a field, it follows field axioms and thus has an additive inverse even though it has no negative numbers. However in theoretical notation (and in SnarkVM) the negative sign is used to denote the inverse element, so it's consistent with common notation in Field theory. The number it ends up though is usually a large number.
For instance, in the ed25519
field (integers mod 2^255 − 19
)
You can write (-2) + 2 = 0
to say "element 2 + its additive inverse equals zero", but it is shorthand notation for (2^255 - 21) + 2 = 0
. Thus (-2) + 2 = (2^255 - 21) + 2 = 0
. This is why using the Neg
trait in SnarkVM is valid, and using it here is valid as well.
Because we are defining a finite field, there are no negatives and negate
isn't a standard term in Field arithmetic, but using the negative operator (as defined in SnarkVM) is consistent. Calling this negate
here is incorrect because it may cause conflation of concepts and lead people to believe this is a signed object and potentially make mistakes in cryptographic code. Inverse
is the operation defined here and that's the name that needs to stay on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation, that helps a lot.
} | ||
|
||
/// Invert the field element. | ||
pub fn inverse(&self) -> Scalar { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be called negate
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should stay inverse
same justification as Field
(Scalar
is also a finite field). Inverse
is the mathematical definition of what's happening here and there's no standard negation
in finite fields.
website/src/workers/worker.js
Outdated
@@ -418,5 +418,7 @@ self.addEventListener("message", (ev) => { | |||
programManager.setHost(defaultHost); | |||
} | |||
})(); | |||
} else if (ev.data.type === "PROPOSE_GAME") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the purpose of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a mistake, I shall remove.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feedback addressed, @Pauan - when there's time to double check, let me know.
} | ||
|
||
/// Invert the field element. | ||
pub fn inverse(&self) -> Field { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Negate isn't the right terminology in Field Theory given there's no general concept of negative
as there is in the real or complex fields. In field theory inverse
has very specific meaning and it's the corresponding member of the field that when added together produces the additive identity element (i.e. 0). If you look in SnarkVM Field traits, inverse
is the terminology used for this as it matches the formal definitions in theory, so this should stay inverse
.
pub fn negate(&self) -> Group { | ||
Group(-self.0) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Pauan the reason this is called negate
is because describes a different operation than the inverse
operation of a field. A group element is an eliptic curve element and a negation
is the definition given to the operation of flipping a point about it axis of symmetry, which is what -self.0
encapsulates. So that's why the difference here.
} | ||
|
||
/// Invert the field element. | ||
pub fn inverse(&self) -> Scalar { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should stay inverse
same justification as Field
(Scalar
is also a finite field). Inverse
is the mathematical definition of what's happening here and there's no standard negation
in finite fields.
website/src/workers/worker.js
Outdated
@@ -418,5 +418,7 @@ self.addEventListener("message", (ev) => { | |||
programManager.setHost(defaultHost); | |||
} | |||
})(); | |||
} else if (ev.data.type === "PROPOSE_GAME") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a mistake, I shall remove.
wasm/src/ledger/transaction.rs
Outdated
let tpk = Group::from(transition.tpk()); | ||
let tcm = Field::from(transition.tcm()); | ||
let scm = Field::from(transition.scm()); | ||
if convert_to_js { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will only work if its first wrapped in JsValue, which seems acceptable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple more minor issues to fix, otherwise it looks good to me.
($($key:literal: $value:expr,)*) => {{ | ||
let object = ::js_sys::Object::new(); | ||
|
||
$(Reflect::set(&object, &::wasm_bindgen::JsValue::from_str($key), &::wasm_bindgen::JsValue::from($value)).unwrap();)* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nit: this should be ::js_sys::Reflect::set
"programId" : argument.program_id().to_string(), | ||
"functionName" : argument.function_name().to_string(), | ||
}; | ||
let arguments = argument |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be moved into the object!
so it can avoid the Reflect::set
.
let record = object! { | ||
"type": "record", | ||
}; | ||
if convert_to_js { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's cleaner to do something like this:
if convert_to_js {
object! {
"type": "record",
...
}
} else {
object! {
"type": "record",
...
}
}
let external_record = object! { | ||
"type": "extneralRecord", | ||
}; | ||
if convert_to_js { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, better to use object!
instead of Reflect::set
}; | ||
|
||
// Create a record object. | ||
if convert_to_js { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, better to use object!
instead of Reflect::set
Motivation
From several requests and issues surrounding the SDK, the following features have been flagged as important to add.
Addition Field, Scalar, and Group Types
Addition of
Field
,Group
, andScalar
types and core arithmetic methods on those types allowing useful work to be done using algebraic types.Accessible Plaintext and Record Data + Conversion of Aleo types to JS type.
This PR adds the ability to access:
Record
directlyStruct
fieldsPlaintext
types into JS objects. This includes AleoLiteral
( i.e.address
,boolean
,i8/i16/i32/i64/i128/u8/u16/u32/u64/u128
,field
,scalar
,signature
),Struct
, andArray
types) into JS types.Transition and Transaction Data Accessor Methods
The ability to search for
records
, and get theinputs
andoutputs
ofTransitions
andTransactions
.Addition of
GraphKey
andComputeKey
This PR adds both the
GraphKey
andComputeKey
to the sdk, allowing GraphKeys to be used directly to determine ownershiprecords
.Test Plan